Index: 3rdParty_sources/reactor/reactor/adapter/JdkFlowAdapter.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/adapter/JdkFlowAdapter.java (revision 0) +++ 3rdParty_sources/reactor/reactor/adapter/JdkFlowAdapter.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2016-2021 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.adapter; + +import java.util.concurrent.Flow; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.core.publisher.Flux; + +/** + * Convert a Java 9+ {@literal Flow.Publisher} to/from a Reactive Streams {@link Publisher}. + * + * @author Stephane Maldini + * @author Sebastien Deleuze + */ +@SuppressWarnings("Since15") +public abstract class JdkFlowAdapter { + + /** + * Return a java {@code Flow.Publisher} from a {@link Flux} + * @param publisher the source Publisher to convert + * @param the type of the publisher + * @return a java {@code Flow.Publisher} from the given {@link Publisher} + */ + public static Flow.Publisher publisherToFlowPublisher(final Publisher + publisher) { + return new PublisherAsFlowPublisher<>(publisher); + } + + /** + * Return a {@link Flux} from a java {@code Flow.Publisher} + * + * @param publisher the source Publisher to convert + * @param the type of the publisher + * @return a {@link Flux} from a java {@code Flow.Publisher} + */ + public static Flux flowPublisherToFlux(Flow.Publisher publisher) { + return new FlowPublisherAsFlux<>(publisher); + } + + private static class FlowPublisherAsFlux extends Flux implements Scannable { + private final java.util.concurrent.Flow.Publisher pub; + + private FlowPublisherAsFlux(java.util.concurrent.Flow.Publisher pub) { + this.pub = pub; + } + + @Override + public void subscribe(final CoreSubscriber actual) { + pub.subscribe(new SubscriberToRS<>(actual)); + } + + @Override + public Object scanUnsafe(Attr key) { + return null; //no particular key to be represented, still useful in hooks + } + } + + private static class PublisherAsFlowPublisher implements Flow.Publisher { + private final Publisher pub; + + private PublisherAsFlowPublisher(Publisher pub) { + this.pub = pub; + } + + @Override + public void subscribe(Flow.Subscriber subscriber) { + pub.subscribe(new FlowSubscriber<>(subscriber)); + } + } + + private static class FlowSubscriber implements CoreSubscriber, Flow.Subscription { + + private final Flow.Subscriber subscriber; + + Subscription subscription; + + public FlowSubscriber(Flow.Subscriber subscriber) { + this.subscriber = subscriber; + } + + @Override + public void onSubscribe(final Subscription s) { + this.subscription = s; + subscriber.onSubscribe(this); + } + + @Override + public void onNext(T o) { + subscriber.onNext(o); + } + + @Override + public void onError(Throwable t) { + subscriber.onError(t); + } + + @Override + public void onComplete() { + subscriber.onComplete(); + } + + @Override + public void request(long n) { + subscription.request(n); + } + + @Override + public void cancel() { + subscription.cancel(); + } + } + + private static class SubscriberToRS implements Flow.Subscriber, Subscription { + + private final Subscriber s; + + Flow.Subscription subscription; + + public SubscriberToRS(Subscriber s) { + this.s = s; + } + + @Override + public void onSubscribe(final Flow.Subscription subscription) { + this.subscription = subscription; + s.onSubscribe(this); + } + + @Override + public void onNext(T o) { + s.onNext(o); + } + + @Override + public void onError(Throwable throwable) { + s.onError(throwable); + } + + @Override + public void onComplete() { + s.onComplete(); + } + + @Override + public void request(long n) { + subscription.request(n); + } + + @Override + public void cancel() { + subscription.cancel(); + } + } + + JdkFlowAdapter(){} +} Index: 3rdParty_sources/reactor/reactor/adapter/package-info.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/adapter/package-info.java (revision 0) +++ 3rdParty_sources/reactor/reactor/adapter/package-info.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +/** + * Adapt + * {@link org.reactivestreams.Publisher} to Java 9+ + * {@link reactor.adapter.JdkFlowAdapter Flow.Publisher}. More adapter can be found + * in reactor-adapter under https://github.com/reactor/reactor-addons/ including RxJava1 and + * RxJava2. + * + * @author Stephane Maldini + */ +@NonNullApi +package reactor.adapter; + +import reactor.util.annotation.NonNullApi; \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/CorePublisher.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/CorePublisher.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/CorePublisher.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016-2021 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; + +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import reactor.core.publisher.Hooks; +import reactor.util.context.Context; + +/** + * A {@link CoreSubscriber} aware publisher. + * + * + * @param the {@link CoreSubscriber} data type + * + * @since 3.3.0 + */ +public interface CorePublisher extends Publisher { + + /** + * An internal {@link Publisher#subscribe(Subscriber)} that will bypass + * {@link Hooks#onLastOperator(Function)} pointcut. + *

+ * In addition to behave as expected by {@link Publisher#subscribe(Subscriber)} + * in a controlled manner, it supports direct subscribe-time {@link Context} passing. + * + * @param subscriber the {@link Subscriber} interested into the published sequence + * @see Publisher#subscribe(Subscriber) + */ + void subscribe(CoreSubscriber subscriber); + +} Index: 3rdParty_sources/reactor/reactor/core/CoreSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/CoreSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/CoreSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2017-2021 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; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.util.context.Context; + +/** + * A {@link Context} aware subscriber which has relaxed rules for §1.3 and §3.9 + * compared to the original {@link org.reactivestreams.Subscriber} from Reactive Streams. + * If an invalid request {@code <= 0} is done on the received subscription, the request + * will not produce an onError and will simply be ignored. + * + *

+ * The rule relaxation has been initially established under reactive-streams-commons. + * + * @param the {@link Subscriber} data type + * + * @since 3.1.0 + */ +public interface CoreSubscriber extends Subscriber { + + /** + * Request a {@link Context} from dependent components which can include downstream + * operators during subscribing or a terminal {@link org.reactivestreams.Subscriber}. + * + * @return a resolved context or {@link Context#empty()} + */ + default Context currentContext(){ + return Context.empty(); + } + + /** + * Implementors should initialize any state used by {@link #onNext(Object)} before + * calling {@link Subscription#request(long)}. Should further {@code onNext} related + * state modification occur, thread-safety will be required. + *

+ * Note that an invalid request {@code <= 0} will not produce an onError and + * will simply be ignored or reported through a debug-enabled + * {@link reactor.util.Logger}. + * + * {@inheritDoc} + */ + @Override + void onSubscribe(Subscription s); +} Index: 3rdParty_sources/reactor/reactor/core/Disposable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/Disposable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/Disposable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2015-2021 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; + +import java.util.Collection; +import java.util.function.Supplier; + +import reactor.util.annotation.Nullable; + +/** + * Indicates that a task or resource can be cancelled/disposed. + *

Call to the dispose method is/should be idempotent. + */ +@FunctionalInterface +public interface Disposable { + + /** + * Cancel or dispose the underlying task or resource. + *

+ * Implementations are required to make this method idempotent. + */ + void dispose(); + + /** + * Optionally return {@literal true} when the resource or task is disposed. + *

+ * Implementations are not required to track disposition and as such may never + * return {@literal true} even when disposed. However, they MUST only return true + * when there's a guarantee the resource or task is disposed. + * + * @return {@literal true} when there's a guarantee the resource or task is disposed. + */ + default boolean isDisposed() { + return false; + } + + /** + * A {@link Disposable} container that allows updating/replacing its inner Disposable + * atomically and with respect of disposing the container itself. + * + * @author Simon Baslé + */ + interface Swap extends Disposable, Supplier { + + /** + * Atomically set the next {@link Disposable} on this container and dispose the previous + * one (if any). If the container has been disposed, fall back to disposing {@code next}. + * + * @param next the {@link Disposable} to set, may be null + * @return true if the operation succeeded, false if the container has been disposed + * @see #replace(Disposable) + */ + boolean update(@Nullable Disposable next); + + /** + * Atomically set the next {@link Disposable} on this container but don't dispose the previous + * one (if any). If the container has been disposed, fall back to disposing {@code next}. + * + * @param next the {@link Disposable} to set, may be null + * @return true if the operation succeeded, false if the container has been disposed + * @see #update(Disposable) + */ + boolean replace(@Nullable Disposable next); + } + + /** + * A container of {@link Disposable} that is itself {@link Disposable}. Accumulate + * disposables and dispose them all in one go by using {@link #dispose()}. Using + * the {@link #add(Disposable)} methods give ownership to the container, which is now + * responsible for disposing them. You can however retake ownership of individual + * elements by keeping a reference and using {@link #remove(Disposable)}, which puts + * the responsibility of disposing said elements back in your hands. Note that once + * disposed, the container cannot be reused and you will need a new {@link Composite}. + * + * @author Simon Baslé + */ + interface Composite extends Disposable { + + /** + * Add a {@link Disposable} to this container, if it is not {@link #isDisposed() disposed}. + * Otherwise d is disposed immediately. + * + * @param d the {@link Disposable} to add. + * @return true if the disposable could be added, false otherwise. + */ + boolean add(Disposable d); + + /** + * Adds the given collection of Disposables to the container or disposes them + * all if the container has been disposed. + * + * @implNote The default implementation is not atomic, meaning that if the container is + * disposed while the content of the collection is added, first elements might be + * effectively added. Stronger consistency is enforced by composites created via + * {@link Disposables#composite()} variants. + * @param ds the collection of Disposables + * @return true if the operation was successful, false if the container has been disposed + */ + default boolean addAll(Collection ds) { + boolean abort = isDisposed(); + for (Disposable d : ds) { + if (abort) { + //multi-add aborted, + d.dispose(); + } + else { + //update the abort flag by attempting to add the disposable + //if not added, it has already been disposed. we abort and will + //dispose all subsequent Disposable + abort = !add(d); + } + } + return !abort; + } + + /** + * Atomically mark the container as {@link #isDisposed() disposed}, clear it and then + * dispose all the previously contained Disposables. From there on the container cannot + * be reused, as {@link #add(Disposable)} and {@link #addAll(Collection)} methods + * will immediately return {@literal false}. + */ + @Override + void dispose(); + + /** + * Indicates if the container has already been disposed. + *

Note that if that is the case, attempts to add new disposable to it via + * {@link #add(Disposable)} and {@link #addAll(Collection)} will be rejected. + * + * @return true if the container has been disposed, false otherwise. + */ + @Override + boolean isDisposed(); + + /** + * Delete the {@link Disposable} from this container, without disposing it. + *

+ * It becomes the responsibility of the caller to dispose the value themselves, + * which they can do by a simple call to {@link Disposable#dispose()} on said + * value (probably guarded by a check that this method returned true, meaning the + * disposable was actually in the container). + * + * @param d the {@link Disposable} to remove. + * @return true if the disposable was successfully deleted, false otherwise. + */ + boolean remove(Disposable d); + + /** + * Returns the number of currently held Disposables. + * @return the number of currently held Disposables + */ + int size(); + } +} Index: 3rdParty_sources/reactor/reactor/core/Disposables.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/Disposables.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/Disposables.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2017-2021 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import reactor.util.annotation.Nullable; + +/** + * A support class that offers factory methods for implementations of the specialized + * {@link Disposable} sub-interfaces ({@link Disposable.Composite Disposable.Composite}, + * {@link Disposable.Swap Disposable.Swap}). + * + * @author Simon Baslé + * @author Stephane Maldini + */ +public final class Disposables { + + private Disposables() { } + + /** + * Create a new empty {@link Disposable.Composite} with atomic guarantees on all mutative + * operations. + * + * @return an empty atomic {@link Disposable.Composite} + */ + public static Disposable.Composite composite() { + return new ListCompositeDisposable(); + } + + /** + * Create and initialize a new {@link Disposable.Composite} with atomic guarantees on + * all mutative operations. + * + * @return a pre-filled atomic {@link Disposable.Composite} + */ + public static Disposable.Composite composite(Disposable... disposables) { + return new ListCompositeDisposable(disposables); + } + + /** + * Create and initialize a new {@link Disposable.Composite} with atomic guarantees on + * all mutative operations. + * + * @return a pre-filled atomic {@link Disposable.Composite} + */ + public static Disposable.Composite composite( + Iterable disposables) { + return new ListCompositeDisposable(disposables); + } + + /** + * Return a new {@link Disposable} that is already disposed. + * + * @return a new disposed {@link Disposable}. + */ + public static Disposable disposed() { + return new AlwaysDisposable(); + } + + /** + * Return a new {@link Disposable} that can never be disposed. Calling {@link Disposable#dispose()} + * is a NO-OP and {@link Disposable#isDisposed()} always return false. + * + * @return a new {@link Disposable} that can never be disposed. + */ + public static Disposable never() { + return new NeverDisposable(); + } + + /** + * Return a new simple {@link Disposable} instance that is initially not disposed but + * can be by calling {@link Disposable#dispose()}. + * + * @return a new {@link Disposable} initially not yet disposed. + */ + public static Disposable single() { + return new SimpleDisposable(); + } + + /** + * Create a new empty {@link Disposable.Swap} with atomic guarantees on all mutative + * operations. + * + * @return an empty atomic {@link Disposable.Swap} + */ + public static Disposable.Swap swap() { + return new SwapDisposable(); + } + + //==== STATIC package private implementations ==== + + /** + * @author David Karnok + * @author Simon Baslé + */ + static final class ListCompositeDisposable implements Disposable.Composite, Scannable { + + @Nullable + List resources; + + volatile boolean disposed; + + ListCompositeDisposable() { + } + + ListCompositeDisposable(Disposable... resources) { + Objects.requireNonNull(resources, "resources is null"); + this.resources = new LinkedList<>(); + for (Disposable d : resources) { + Objects.requireNonNull(d, "Disposable item is null"); + this.resources.add(d); + } + } + + ListCompositeDisposable(Iterable resources) { + Objects.requireNonNull(resources, "resources is null"); + this.resources = new LinkedList<>(); + for (Disposable d : resources) { + Objects.requireNonNull(d, "Disposable item is null"); + this.resources.add(d); + } + } + + @Override + public void dispose() { + if (disposed) { + return; + } + List set; + synchronized (this) { + if (disposed) { + return; + } + disposed = true; + set = resources; + resources = null; + } + + dispose(set); + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public boolean add(Disposable d) { + Objects.requireNonNull(d, "d is null"); + if (!disposed) { + synchronized (this) { + if (!disposed) { + List set = resources; + if (set == null) { + set = new LinkedList<>(); + resources = set; + } + set.add(d); + return true; + } + } + } + d.dispose(); + return false; + } + + @Override + public boolean addAll(Collection ds) { + Objects.requireNonNull(ds, "ds is null"); + if (!disposed) { + synchronized (this) { + if (!disposed) { + List set = resources; + if (set == null) { + set = new LinkedList<>(); + resources = set; + } + for (Disposable d : ds) { + Objects.requireNonNull(d, "d is null"); + set.add(d); + } + return true; + } + } + } + for (Disposable d : ds) { + d.dispose(); + } + return false; + } + + @Override + public boolean remove(Disposable d) { + Objects.requireNonNull(d, "Disposable item is null"); + if (disposed) { + return false; + } + synchronized (this) { + if (disposed) { + return false; + } + + List set = resources; + if (set == null || !set.remove(d)) { + return false; + } + } + return true; + } + + @Override + public int size() { + List r = resources; + return r == null ? 0 : r.size(); + } + + Stream asStream() { + List r = resources; + return r == null ? Stream.empty() : r.stream(); + } + + public void clear() { + if (disposed) { + return; + } + List set; + synchronized (this) { + if (disposed) { + return; + } + + set = resources; + resources = null; + } + + dispose(set); + } + + void dispose(@Nullable List set) { + if (set == null) { + return; + } + List errors = null; + for (Disposable o : set) { + try { + o.dispose(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + if (errors == null) { + errors = new ArrayList<>(); + } + errors.add(ex); + } + } + if (errors != null) { + if (errors.size() == 1) { + throw Exceptions.propagate(errors.get(0)); + } + throw Exceptions.multiple(errors); + } + } + + @Override + public Stream inners() { + return this.asStream() + .filter(Scannable.class::isInstance) + .map(Scannable::from); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) { + return isDisposed(); + } + return null; + } + } + +/** + * Not public API. Implementation of a {@link Swap}. + * + * @author Simon Baslé + * @author David Karnok + */ +static final class SwapDisposable implements Disposable.Swap { + + volatile Disposable inner; + static final AtomicReferenceFieldUpdater + INNER = + AtomicReferenceFieldUpdater.newUpdater(SwapDisposable.class, Disposable.class, "inner"); + + @Override + public boolean update(@Nullable Disposable next) { + return Disposables.set(INNER, this, next); + } + + @Override + public boolean replace(@Nullable Disposable next) { + return Disposables.replace(INNER, this, next); + } + + @Override + @Nullable + public Disposable get() { + return inner; + } + + @Override + public void dispose() { + Disposables.dispose(INNER, this); + } + + @Override + public boolean isDisposed() { + return Disposables.isDisposed(INNER.get(this)); + } +} + +/** + * A very simple {@link Disposable} that only wraps a mutable boolean for + * {@link #isDisposed()}. + */ +static final class SimpleDisposable extends AtomicBoolean implements Disposable { + + @Override + public void dispose() { + set(true); + } + + @Override + public boolean isDisposed() { + return get(); + } +} + +/** + * Immutable disposable that is always {@link #isDisposed() disposed}. Calling + * {@link #dispose()} does nothing, and {@link #isDisposed()} always return true. + */ +static final class AlwaysDisposable implements Disposable { + + @Override + public void dispose() { + //NO-OP + } + + @Override + public boolean isDisposed() { + return true; + } +} + +/** + * Immutable disposable that is never {@link #isDisposed() disposed}. Calling + * {@link #dispose()} does nothing, and {@link #isDisposed()} always return false. + */ +static final class NeverDisposable implements Disposable { + + @Override + public void dispose() { + //NO-OP + } + + @Override + public boolean isDisposed() { + return false; + } +} + + //==== STATIC ATOMIC UTILS copied from Disposables ==== + + /** + * A singleton {@link Disposable} that represents a disposed instance. Should not be + * leaked to clients. + */ + //NOTE: There is a private similar DISPOSED singleton in Disposables as well + static final Disposable DISPOSED = disposed(); + + /** + * Atomically set the field to a {@link Disposable} and dispose the old content. + * + * @param updater the target field updater + * @param holder the target instance holding the field + * @param newValue the new Disposable to set + * @return true if successful, false if the field contains the {@link #DISPOSED} instance. + */ + static boolean set(AtomicReferenceFieldUpdater updater, T holder, @Nullable Disposable newValue) { + for (;;) { + Disposable current = updater.get(holder); + if (current == DISPOSED) { + if (newValue != null) { + newValue.dispose(); + } + return false; + } + if (updater.compareAndSet(holder, current, newValue)) { + if (current != null) { + current.dispose(); + } + return true; + } + } + } + + /** + * Atomically replace the {@link Disposable} in the field with the given new Disposable + * but do not dispose the old one. + * + * @param updater the target field updater + * @param holder the target instance holding the field + * @param newValue the new Disposable to set, null allowed + * @return true if the operation succeeded, false if the target field contained + * the common {@link #DISPOSED} instance and the given disposable is not null but is disposed. + */ + static boolean replace(AtomicReferenceFieldUpdater updater, T holder, @Nullable Disposable newValue) { + for (;;) { + Disposable current = updater.get(holder); + if (current == DISPOSED) { + if (newValue != null) { + newValue.dispose(); + } + return false; + } + if (updater.compareAndSet(holder, current, newValue)) { + return true; + } + } + } + + /** + * Atomically dispose the {@link Disposable} in the field if not already disposed. + * + * @param updater the target field updater + * @param holder the target instance holding the field + * @return true if the {@link Disposable} held by the field was properly disposed + */ + static boolean dispose(AtomicReferenceFieldUpdater updater, T holder) { + Disposable current = updater.get(holder); + Disposable d = DISPOSED; + if (current != d) { + current = updater.getAndSet(holder, d); + if (current != d) { + if (current != null) { + current.dispose(); + } + return true; + } + } + return false; + } + + /** + * Check if the given {@link Disposable} is the singleton {@link #DISPOSED}. + * + * @param d the disposable to check + * @return true if d is {@link #DISPOSED} + */ + static boolean isDisposed(Disposable d) { + return d == DISPOSED; + } + //===================================================== +} Index: 3rdParty_sources/reactor/reactor/core/Exceptions.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/Exceptions.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/Exceptions.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,766 @@ +/* + * Copyright (c) 2016-2021 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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import reactor.core.publisher.Flux; +import reactor.util.annotation.Nullable; +import reactor.util.retry.Retry; + +/** + * Global Reactor Core Exception handling and utils to operate on. + * + * @author Stephane Maldini + * @see Reactive-Streams-Commons + */ +public abstract class Exceptions { + + /** + * 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. + */ + public static final String BACKPRESSURE_ERROR_QUEUE_FULL = "Queue is full: Reactive Streams source doesn't respect backpressure"; + + /** + * A singleton instance of a Throwable indicating a terminal state for exceptions, + * don't leak this! + */ + @SuppressWarnings("ThrowableInstanceNeverThrown") + public static final Throwable TERMINATED = new StaticThrowable("Operator has been terminated"); + + /** + * Update an empty atomic reference with the given exception, or combine further added + * exceptions together as suppressed exceptions under a root Throwable with + * the {@code "Multiple exceptions"} message, if the atomic reference already holds + * one. This is short-circuited if the reference contains {@link #TERMINATED}. + *

+ * Since composite exceptions and traceback exceptions share the same underlying mechanism + * of suppressed exceptions, a traceback could be made part of a composite exception. + * Use {@link #unwrapMultipleExcludingTracebacks(Throwable)} to filter out such elements in + * a composite if needed. + * + * @param the parent instance type + * @param field the target field updater + * @param instance the parent instance for the field + * @param exception the Throwable to add. + * + * @return true if added, false if the field contained the {@link #TERMINATED} + * instance. + * @see #unwrapMultiple(Throwable) + */ + public static boolean addThrowable(AtomicReferenceFieldUpdater field, + T instance, + Throwable exception) { + for (; ; ) { + Throwable current = field.get(instance); + + if (current == TERMINATED) { + return false; + } + + if (current instanceof CompositeException) { + //this is ok, composite exceptions are never singletons + current.addSuppressed(exception); + return true; + } + + Throwable update; + if (current == null) { + update = exception; + } else { + update = multiple(current, exception); + } + + if (field.compareAndSet(instance, current, update)) { + return true; + } + } + } + + /** + * 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 + * will correctly unwrap these to a {@link List} of the suppressed exceptions. Note + * that is will also be consistent in producing a List for other types of exceptions + * by putting the input inside a single-element List. + *

+ * Since composite exceptions and traceback exceptions share the same underlying mechanism + * of suppressed exceptions, a traceback could be made part of a composite exception. + * Use {@link #unwrapMultipleExcludingTracebacks(Throwable)} to filter out such elements in + * a composite if needed. + * + * @param throwables the exceptions to wrap into a composite + * @return a composite exception with a standard message, and the given throwables as + * suppressed exceptions + * @see #addThrowable(AtomicReferenceFieldUpdater, Object, Throwable) + */ + public static RuntimeException multiple(Throwable... throwables) { + CompositeException multiple = new CompositeException(); + //noinspection ConstantConditions + if (throwables != null) { + for (Throwable t : throwables) { + //this is ok, multiple is always a new non-singleton instance + multiple.addSuppressed(t); + } + } + return multiple; + } + + /** + * Create a composite exception that wraps the given {@link Throwable Throwable(s)}, + * as suppressed exceptions. Instances created by this method can be detected using the + * {@link #isMultiple(Throwable)} check. The {@link #unwrapMultiple(Throwable)} method + * will correctly unwrap these to a {@link List} of the suppressed exceptions. Note + * that is will also be consistent in producing a List for other types of exceptions + * by putting the input inside a single-element List. + *

+ * Since composite exceptions and traceback exceptions share the same underlying mechanism + * of suppressed exceptions, a traceback could be made part of a composite exception. + * Use {@link #unwrapMultipleExcludingTracebacks(Throwable)} to filter out such elements in + * a composite if needed. + * + * @param throwables the exceptions to wrap into a composite + * @return a composite exception with a standard message, and the given throwables as + * suppressed exceptions + * @see #addThrowable(AtomicReferenceFieldUpdater, Object, Throwable) + */ + public static RuntimeException multiple(Iterable throwables) { + CompositeException multiple = new CompositeException(); + //noinspection ConstantConditions + if (throwables != null) { + for (Throwable t : throwables) { + //this is ok, multiple is always a new non-singleton instance + multiple.addSuppressed(t); + } + } + return multiple; + } + + /** + * Prepare an unchecked {@link RuntimeException} that will bubble upstream if thrown + * by an operator.

This method invokes {@link #throwIfFatal(Throwable)}. + * + * @param t the root cause + * + * @return an unchecked exception that should choose bubbling up over error callback + * path + */ + public static RuntimeException bubble(Throwable t) { + throwIfFatal(t); + return new BubblingException(t); + } + + /** + * @return a new {@link IllegalStateException} with a cause message abiding to + * reactive stream specification rule 2.12. + */ + public static IllegalStateException duplicateOnSubscribeException() { + return new IllegalStateException( + "Spec. Rule 2.12 - Subscriber.onSubscribe MUST NOT be called more than once (based on object equality)"); + } + + /** + * Return an {@link UnsupportedOperationException} indicating that the error callback + * on a subscriber was not implemented, yet an error was propagated. + * + * @param cause original error not processed by a receiver. + * @return an {@link UnsupportedOperationException} indicating the error callback was + * not implemented and holding the original propagated error. + * @see #isErrorCallbackNotImplemented(Throwable) + */ + public static UnsupportedOperationException errorCallbackNotImplemented(Throwable cause) { + Objects.requireNonNull(cause, "cause"); + return new ErrorCallbackNotImplemented(cause); + } + + /** + * An exception that is propagated upward and considered as "fatal" as per Reactive + * Stream limited list of exceptions allowed to bubble. It is not meant to be common + * error resolution but might assist implementors in dealing with boundaries (queues, + * combinations and async). + * + * @return a {@link RuntimeException} that can be identified via {@link #isCancel} + * @see #isCancel(Throwable) + */ + public static RuntimeException failWithCancel() { + return new CancelException(); + } + + /** + * Return an {@link IllegalStateException} indicating the receiver is overrun by + * more signals than expected in case of a bounded queue, or more generally that data + * couldn't be emitted due to a lack of request + * + * @return an {@link IllegalStateException} + * @see #isOverflow(Throwable) + */ + public static IllegalStateException failWithOverflow() { + return new OverflowException("The receiver is overrun by more signals than expected (bounded queue...)"); + } + + /** + * Return an {@link IllegalStateException} indicating the receiver is overrun by + * more signals than expected in case of a bounded queue or more generally that data + * couldn't be emitted due to a lack of request + * + * @param message the exception's message + * @return an {@link IllegalStateException} + * @see #isOverflow(Throwable) + */ + public static IllegalStateException failWithOverflow(String message) { + return new OverflowException(message); + } + + /** + * Return a singleton {@link RejectedExecutionException} + * + * @return a singleton {@link RejectedExecutionException} + */ + public static RejectedExecutionException failWithRejected() { + return REJECTED_EXECUTION; + } + + /** + * Return a singleton {@link RejectedExecutionException} with a message indicating + * the reason is due to the scheduler not being time-capable + * + * @return a singleton {@link RejectedExecutionException} + */ + public static RejectedExecutionException failWithRejectedNotTimeCapable() { + return NOT_TIME_CAPABLE_REJECTED_EXECUTION; + } + + /** + * Return a new {@link RejectedExecutionException} with standard message and cause, + * unless the {@code cause} is already a {@link RejectedExecutionException} created + * via {@link #failWithRejected(Throwable)} (not the singleton-producing variants). + * + * @param cause the original exception that caused the rejection + * @return a new {@link RejectedExecutionException} with standard message and cause + */ + public static RejectedExecutionException failWithRejected(Throwable cause) { + if (cause instanceof ReactorRejectedExecutionException) { + return (RejectedExecutionException) cause; + } + return new ReactorRejectedExecutionException("Scheduler unavailable", cause); + } + + /** + * Return a new {@link RejectedExecutionException} with given message. + * + * @param message the rejection message + * @return a new {@link RejectedExecutionException} with custom message + */ + public static RejectedExecutionException failWithRejected(String message) { + return new ReactorRejectedExecutionException(message); + } + + /** + * Return a new {@link RuntimeException} that represents too many failures on retry. + * This nature can be detected via {@link #isRetryExhausted(Throwable)}. + * The cause of the last retry attempt is passed and stored as this exception's {@link Throwable#getCause() cause}. + * + * @param message the message + * @param cause the cause of the last retry attempt that failed (or null if irrelevant) + * @return a new {@link RuntimeException} representing retry exhaustion due to too many attempts + */ + public static RuntimeException retryExhausted(String message, @Nullable Throwable cause) { + return cause == null ? new RetryExhaustedException(message) : new RetryExhaustedException(message, cause); + } + + /** + * Check if the given exception represents an {@link #failWithOverflow() overflow}. + * @param t the {@link Throwable} error to check + * @return true if the given {@link Throwable} represents an overflow. + */ + public static boolean isOverflow(@Nullable Throwable t) { + return t instanceof OverflowException; + } + + /** + * Check if the given exception is a {@link #bubble(Throwable) bubbled} wrapped exception. + * @param t the {@link Throwable} error to check + * @return true if given {@link Throwable} is a bubbled wrapped exception. + */ + public static boolean isBubbling(@Nullable Throwable t) { + return t instanceof BubblingException; + } + + /** + * Check if the given error is a {@link #failWithCancel() cancel signal}. + * @param t the {@link Throwable} error to check + * @return true if given {@link Throwable} is a cancellation token. + */ + public static boolean isCancel(@Nullable Throwable t) { + return t instanceof CancelException; + } + + /** + * Check if the given error is a {@link #errorCallbackNotImplemented(Throwable) callback not implemented} + * exception, in which case its {@link Throwable#getCause() cause} will be the propagated + * error that couldn't be processed. + * + * @param t the {@link Throwable} error to check + * @return true if given {@link Throwable} is a callback not implemented exception. + */ + public static boolean isErrorCallbackNotImplemented(@Nullable Throwable t) { + return t instanceof ErrorCallbackNotImplemented; + } + + /** + * Check a {@link Throwable} to see if it is a composite, as created by {@link #multiple(Throwable...)}. + * + * @param t the {@link Throwable} to check, {@literal null} always yields {@literal false} + * @return true if the Throwable is an instance created by {@link #multiple(Throwable...)}, false otherwise + */ + public static boolean isMultiple(@Nullable Throwable t) { + return t instanceof CompositeException; + } + + /** + * Check a {@link Throwable} to see if it indicates too many retry attempts have failed. + * Such an exception can be created via {@link #retryExhausted(String, Throwable)}. + * + * @param t the {@link Throwable} to check, {@literal null} always yields {@literal false} + * @return true if the Throwable is an instance representing retry exhaustion, false otherwise + */ + public static boolean isRetryExhausted(@Nullable Throwable t) { + return t instanceof RetryExhaustedException; + } + + /** + * Check a {@link Throwable} to see if it is a traceback, as created by the checkpoint operator or debug utilities. + * + * @param t the {@link Throwable} to check, {@literal null} always yields {@literal false} + * @return true if the Throwable is a traceback, false otherwise + */ + public static boolean isTraceback(@Nullable Throwable t) { + if (t == null) { + return false; + } + //FIXME maybe add an interface here for detection purposes + return "reactor.core.publisher.FluxOnAssembly.OnAssemblyException".equals(t.getClass().getCanonicalName()); + } + + /** + * @param elements the invalid requested demand + * + * @return a new {@link IllegalArgumentException} with a cause message abiding to + * reactive stream specification rule 3.9. + */ + public static IllegalArgumentException nullOrNegativeRequestException(long elements) { + return new IllegalArgumentException( + "Spec. Rule 3.9 - Cannot request a non strictly positive number: " + elements); + } + + /** + * Prepare an unchecked {@link RuntimeException} that should be propagated + * downstream through {@link org.reactivestreams.Subscriber#onError(Throwable)}. + *

This method invokes {@link #throwIfFatal(Throwable)}. + * + * @param t the root cause + * + * @return an unchecked exception to propagate through onError signals. + */ + public static RuntimeException propagate(Throwable t) { + throwIfFatal(t); + if (t instanceof RuntimeException) { + return (RuntimeException) t; + } + return new ReactiveException(t); + } + + /** + * Atomic utility to safely mark a volatile throwable reference with a terminal + * marker. + * + * @param field the atomic container + * @param instance the reference instance + * @param the instance type + * + * @return the previously masked throwable + */ + @Nullable + public static Throwable terminate(AtomicReferenceFieldUpdater field, + T instance) { + Throwable current = field.get(instance); + if (current != TERMINATED) { + current = field.getAndSet(instance, TERMINATED); + } + return current; + } + + /** + * 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)})
  • + *
  • {@code ErrorCallbackNotImplemented} (as detectable by {@link #isErrorCallbackNotImplemented(Throwable)})
  • + *
  • {@link VirtualMachineError}
  • {@link ThreadDeath}
  • {@link LinkageError}
+ * + * @param t the exception to evaluate + */ + public static void throwIfFatal(@Nullable Throwable t) { + if (t instanceof BubblingException) { + throw (BubblingException) t; + } + if (t instanceof ErrorCallbackNotImplemented) { + throw (ErrorCallbackNotImplemented) t; + } + throwIfJvmFatal(t); + } + + /** + * Throws a particular {@code Throwable} only if it belongs to a set of "fatal" error + * varieties native to the JVM. These varieties are as follows: + *
  • {@link VirtualMachineError}
  • {@link ThreadDeath}
  • + *
  • {@link LinkageError}
+ * + * @param t the exception to evaluate + */ + public static void throwIfJvmFatal(@Nullable Throwable t) { + if (t instanceof VirtualMachineError) { + throw (VirtualMachineError) t; + } + if (t instanceof ThreadDeath) { + throw (ThreadDeath) t; + } + if (t instanceof LinkageError) { + throw (LinkageError) t; + } + } + + /** + * Unwrap a particular {@code Throwable} only if it is was wrapped via + * {@link #bubble(Throwable) bubble} or {@link #propagate(Throwable) propagate}. + * + * @param t the exception to unwrap + * + * @return the unwrapped exception or current one if null + */ + public static Throwable unwrap(Throwable t) { + Throwable _t = t; + while (_t instanceof ReactiveException) { + _t = _t.getCause(); + } + return _t == null ? t : _t; + } + + /** + * Attempt to unwrap a {@link Throwable} into a {@link List} of Throwables. This is + * only done on the condition that said Throwable is a composite exception built by + * {@link #multiple(Throwable...)}, in which case the list contains the exceptions + * wrapped as suppressed exceptions in the composite. In any other case, the list + * only contains the input Throwable (or is empty in case of null input). + *

+ * Since composite exceptions and traceback exceptions share the same underlying mechanism + * of suppressed exceptions, a traceback could be made part of a composite exception. + * Use {@link #unwrapMultipleExcludingTracebacks(Throwable)} to filter out such elements in + * a composite if needed. + * + * @param potentialMultiple the {@link Throwable} to unwrap if multiple + * @return a {@link List} of the exceptions suppressed by the {@link Throwable} if + * multiple, or a List containing the Throwable otherwise. Null input results in an + * empty List. + * @see #unwrapMultipleExcludingTracebacks(Throwable) + */ + public static List unwrapMultiple(@Nullable Throwable potentialMultiple) { + if (potentialMultiple == null) { + return Collections.emptyList(); + } + + if (isMultiple(potentialMultiple)) { + return Arrays.asList(potentialMultiple.getSuppressed()); + } + + return Collections.singletonList(potentialMultiple); + } + + /** + * Attempt to unwrap a {@link Throwable} into a {@link List} of Throwables, excluding instances that + * are {@link #isTraceback(Throwable) tracebacks}. + * This is only done on the condition that said Throwable is a composite exception built by + * {@link #multiple(Throwable...)}, in which case the returned list contains its suppressed exceptions + * minus the tracebacks. In any other case, the list only contains the input Throwable (or is empty in + * case of null input). + *

+ * This is useful because tracebacks are added as suppressed exceptions and thus can appear as components + * of a composite. + * + * @param potentialMultiple the {@link Throwable} to unwrap if multiple + * @return a {@link List} of the exceptions suppressed by the {@link Throwable} minus the + * tracebacks if multiple, or a List containing the Throwable otherwise. Null input results in an + * empty List. + */ + public static List unwrapMultipleExcludingTracebacks(@Nullable Throwable potentialMultiple) { + if (potentialMultiple == null) { + return Collections.emptyList(); + } + + if (isMultiple(potentialMultiple)) { + final Throwable[] suppressed = potentialMultiple.getSuppressed(); + List filtered = new ArrayList<>(suppressed.length); + for (Throwable t : suppressed) { + if (isTraceback(t)) { + continue; + } + filtered.add(t); + } + return filtered; + } + + return Collections.singletonList(potentialMultiple); + } + + /** + * Safely suppress a {@link Throwable} on a {@link RuntimeException}. The returned + * {@link RuntimeException}, bearing the suppressed exception, is most often the same + * as the original exception unless: + *

    + *
  • original and tentatively suppressed exceptions are one and the same: do + * nothing but return the original.
  • + *
  • original is one of the singleton {@link RejectedExecutionException} created + * by Reactor: make a copy the {@link RejectedExecutionException}, add the + * suppressed exception to it and return that copy.
  • + * + *
+ * @param original the original {@link RuntimeException} to bear a suppressed exception + * @param suppressed the {@link Throwable} to suppress + * @return the (most of the time original) {@link RuntimeException} bearing the + * suppressed {@link Throwable} + */ + public static final RuntimeException addSuppressed(RuntimeException original, Throwable suppressed) { + if (original == suppressed) { + return original; + } + if (original == REJECTED_EXECUTION || original == NOT_TIME_CAPABLE_REJECTED_EXECUTION) { + RejectedExecutionException ree = new RejectedExecutionException(original.getMessage()); + ree.addSuppressed(suppressed); + return ree; + } + else { + original.addSuppressed(suppressed); + return original; + } + } + + /** + * Safely suppress a {@link Throwable} on an original {@link Throwable}. The returned + * {@link Throwable}, bearing the suppressed exception, is most often the same + * as the original one unless: + *
    + *
  • original and tentatively suppressed exceptions are one and the same: do + * nothing but return the original.
  • + *
  • original is one of the singleton {@link RejectedExecutionException} created + * by Reactor: make a copy the {@link RejectedExecutionException}, add the + * suppressed exception to it and return that copy.
  • + *
  • original is a special internal TERMINATED singleton instance: return it + * without suppressing the exception.
  • + * + *
+ * @param original the original {@link Throwable} to bear a suppressed exception + * @param suppressed the {@link Throwable} to suppress + * @return the (most of the time original) {@link Throwable} bearing the + * suppressed {@link Throwable} + */ + public static final Throwable addSuppressed(Throwable original, final Throwable suppressed) { + if (original == suppressed) { + return original; + } + + if (original == TERMINATED) { + return original; + } + + if (original == REJECTED_EXECUTION || original == NOT_TIME_CAPABLE_REJECTED_EXECUTION) { + RejectedExecutionException ree = new RejectedExecutionException(original.getMessage()); + ree.addSuppressed(suppressed); + return ree; + } + else { + original.addSuppressed(suppressed); + return original; + } + } + + Exceptions() { + } + + static final RejectedExecutionException REJECTED_EXECUTION = new StaticRejectedExecutionException("Scheduler unavailable"); + + static final RejectedExecutionException NOT_TIME_CAPABLE_REJECTED_EXECUTION = + new StaticRejectedExecutionException("Scheduler is not capable of time-based scheduling"); + + static class CompositeException extends ReactiveException { + + CompositeException() { + super("Multiple exceptions"); + } + + private static final long serialVersionUID = 8070744939537687606L; + } + + static class BubblingException extends ReactiveException { + + BubblingException(String message) { + super(message); + } + + BubblingException(Throwable cause) { + super(cause); + } + + private static final long serialVersionUID = 2491425277432776142L; + } + + /** + * An exception that is propagated downward through {@link org.reactivestreams.Subscriber#onError(Throwable)} + */ + static class ReactiveException extends RuntimeException { + + ReactiveException(Throwable cause) { + super(cause); + } + + ReactiveException(String message) { + super(message); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return getCause() != null ? getCause().fillInStackTrace() : + super.fillInStackTrace(); + } + + private static final long serialVersionUID = 2491425227432776143L; + } + + static final class ErrorCallbackNotImplemented extends UnsupportedOperationException { + + ErrorCallbackNotImplemented(Throwable cause) { + super(cause); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + private static final long serialVersionUID = 2491425227432776143L; + } + + /** + * An error signal from downstream subscribers consuming data when their state is + * denying any additional event. + * + * @author Stephane Maldini + */ + static final class CancelException extends BubblingException { + + CancelException() { + super("The subscriber has denied dispatching"); + } + + private static final long serialVersionUID = 2491425227432776144L; + + } + + static final class OverflowException extends IllegalStateException { + + OverflowException(String s) { + super(s); + } + } + + /** + * A specialized {@link IllegalStateException} to signify a {@link Flux#retryWhen(Retry) retry} + * has failed (eg. after N attempts, or a timeout). + * + * @see #retryExhausted(String, Throwable) + * @see #isRetryExhausted(Throwable) + */ + static final class RetryExhaustedException extends IllegalStateException { + + RetryExhaustedException(String message) { + super(message); + } + + RetryExhaustedException(String message, Throwable cause) { + super(message, cause); + } + } + + static class ReactorRejectedExecutionException extends RejectedExecutionException { + + ReactorRejectedExecutionException(String message, Throwable cause) { + super(message, cause); + } + + ReactorRejectedExecutionException(String message) { + super(message); + } + } + + /** + * A {@link RejectedExecutionException} that is tailored for usage as a static final + * field. It avoids {@link ClassLoader}-related leaks by bypassing stacktrace filling. + */ + static final class StaticRejectedExecutionException extends RejectedExecutionException { + + StaticRejectedExecutionException(String message, Throwable cause) { + super(message, cause); + } + + StaticRejectedExecutionException(String message) { + super(message); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + } + + /** + * A general-purpose {@link Throwable} that is suitable for usage as a static final + * field. It avoids {@link ClassLoader}-related leaks by bypassing stacktrace filling. + * Exception {{@link Exception#addSuppressed(Throwable)} suppression} is also disabled. + */ + //see https://github.com/reactor/reactor-core/pull/1872 + static final class StaticThrowable extends Error { + + StaticThrowable(String message) { + super(message, null, false, false); + } + + StaticThrowable(String message, Throwable cause) { + super(message, cause, false, false); + } + + StaticThrowable(Throwable cause) { + super(cause.toString(), cause, false, false); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/Fuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/Fuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/Fuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2016-2021 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; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.Callable; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.util.annotation.Nullable; + +/** + * A micro API for stream fusion, in particular marks producers that support a {@link QueueSubscription}. + */ +public interface Fuseable { + + /** Indicates the QueueSubscription can't support the requested mode. */ + int NONE = 0; + /** Indicates the QueueSubscription can perform sync-fusion. */ + int SYNC = 1; + /** Indicates the QueueSubscription can perform only async-fusion. */ + int ASYNC = 2; + /** Indicates the QueueSubscription should decide what fusion it performs (input only). */ + int ANY = 3; + /** + * Indicates that the queue will be drained from another thread + * thus any queue-exit computation may be invalid at that point. + *

+ * For example, an {@code asyncSource.map().publishOn().subscribe()} sequence where {@code asyncSource} + * is async-fuseable: publishOn may fuse the whole sequence into a single Queue. That in turn + * could invoke the mapper function from its {@code poll()} method from another thread, + * whereas the unfused sequence would have invoked the mapper on the previous thread. + * If such mapper invocation is costly, it would escape its thread boundary this way. + */ + int THREAD_BARRIER = 0b100; //4 + + /** + * Attempt to convert a fusion mode int code into a human-readable representation. + * Note that this can include the {@link #THREAD_BARRIER} flag, as an appended {@code +THREAD_BARRIER}. + *

+ * This method accepts {@code -1} as a special code, mainly for the benefit of supporting testing scenarios where + * fusion can be entirely deactivated (returns {@code Disabled}). + * Other negative values and unknown positive codes on the other hand return {@code Unknown(x)}. + *

+ * Note that as this is a human-facing representation, the different values could evolve in the future. + * As such, never compare the returned string to a constant, but always use this method on both sides of + * a comparison. + * + * @param mode the fusion mode int code + * @return a human-readable {@link String} representation of the code + */ + static String fusionModeName(int mode) { + return fusionModeName(mode, false); + } + + /** + * Attempt to convert a fusion mode int code into a human-readable representation. + * Note that this can include the {@link #THREAD_BARRIER} flag, as an appended {@code +THREAD_BARRIER}, + * unless the {@code ignoreThreadBarrier} parameter is set to {@literal true}. + *

+ * This method accepts {@code -1} as a special code, mainly for the benefit of supporting testing scenarios where + * fusion can be entirely deactivated (returns {@code Disabled}). + * Other negative values and unknown positive codes on the other hand return {@code Unknown(x)}. + *

+ * Note that as this is a human-facing representation, the different values could evolve in the future. + * As such, never compare the returned string to a constant, but always use this method on both sides of + * a comparison. + * + * @param mode the fusion mode int code + * @param ignoreThreadBarrier whether or not to ignore the {@link #THREAD_BARRIER} flag in the representation + * @return a human-readable {@link String} representation of the code + */ + static String fusionModeName(int mode, boolean ignoreThreadBarrier) { + int evaluated = mode; + String threadBarrierSuffix = ""; + if (mode >= 0) { + evaluated = mode & ~THREAD_BARRIER; //erase the THREAD_BARRIER bit; + if (!ignoreThreadBarrier && (mode & THREAD_BARRIER) == THREAD_BARRIER) { + threadBarrierSuffix = "+THREAD_BARRIER"; + } + } + + switch (evaluated) { + case -1: + return "Disabled"; //this is more specific for tests or things that can entirely skip the fusion negotiation + case Fuseable.NONE: + return "NONE" + threadBarrierSuffix; + case Fuseable.SYNC: + return "SYNC" + threadBarrierSuffix; + case Fuseable.ASYNC: + return "ASYNC" + threadBarrierSuffix; + default: + return "Unknown(" + evaluated + ")" + threadBarrierSuffix; + } + } + + /** + * A subscriber variant that can immediately tell if it consumed + * the value or not, directly allowing a new value to be sent if + * it didn't. This avoids the usual request(1) round-trip for dropped + * values. + * + * @param the value type + */ + interface ConditionalSubscriber extends CoreSubscriber { + /** + * Try consuming the value and return true if successful. + * @param t the value to consume, not null + * @return true if consumed, false if dropped and a new value can be immediately sent + */ + boolean tryOnNext(T t); + } + + /** + * Support contract for queue-fusion based optimizations on subscriptions. + * + *

    + *
  • + * Synchronous sources which have fixed size and can + * emit their items in a pull fashion, thus avoiding the request-accounting + * overhead in many cases. + *
  • + *
  • + * Asynchronous sources which can act as a queue and subscription at + * the same time, saving on allocating another queue most of the time. + *
  • + *
+ * + *

+ * + * @param the value type emitted + */ + interface QueueSubscription extends Queue, Subscription { + + String NOT_SUPPORTED_MESSAGE = "Although QueueSubscription extends Queue it is purely internal" + + " and only guarantees support for poll/clear/size/isEmpty." + + " Instances shouldn't be used/exposed as Queue outside of Reactor operators."; + + /** + * Request a specific fusion mode from this QueueSubscription. + *

+ * One should request either SYNC, ASYNC or ANY modes (never NONE) + * and the implementor should return NONE, SYNC or ASYNC (never ANY). + *

+ * For example, if a source supports only ASYNC fusion but + * the intermediate operator supports only SYNC fuseable sources, + * the operator may request SYNC fusion and the source can reject it via + * NONE, thus the operator can return NONE as well to downstream and the + * fusion doesn't happen. + * + * @param requestedMode the mode requested by the intermediate operator + * @return the actual fusion mode activated + */ + int requestFusion(int requestedMode); + + + @Override + @Nullable + default T peek() { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default boolean add(@Nullable T t) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default boolean offer(@Nullable T t) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default T remove() { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default T element() { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default boolean contains(@Nullable Object o) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default Iterator iterator() { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default Object[] toArray() { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default T1[] toArray(T1[] a) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default boolean remove(@Nullable Object o) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default boolean containsAll(Collection c) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default boolean addAll(Collection c) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default boolean removeAll(Collection c) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + + @Override + default boolean retainAll(Collection c) { + throw new UnsupportedOperationException(NOT_SUPPORTED_MESSAGE); + } + } + + /** + * Base class for synchronous sources which have fixed size and can + * emit their items in a pull fashion, thus avoiding the request-accounting + * overhead in many cases. + * + * @param the content value type + */ + interface SynchronousSubscription extends QueueSubscription { + + @Override + default int requestFusion(int requestedMode) { + if ((requestedMode & Fuseable.SYNC) != 0) { + return Fuseable.SYNC; + } + return NONE; + } + + } + + /** + * Marker interface indicating that the target can return a value or null, + * otherwise fail immediately and thus a viable target for assembly-time + * optimizations. + * + * @param the value type returned + */ + interface ScalarCallable extends Callable { } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/Scannable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/Scannable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/Scannable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,612 @@ +/* + * Copyright (c) 2017-2021 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; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Spliterators; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.reactivestreams.Subscriber; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Scheduler.Worker; +import reactor.util.annotation.Nullable; +import reactor.util.function.Tuple2; + +/** + * A Scannable component exposes state in a non strictly memory consistent way and + * results should be understood as best-effort hint of the underlying state. This is + * useful to retro-engineer a component graph such as a flux operator chain via + * {@link Stream} queries from + * {@link #actuals()}, {@link #parents()} and {@link #inners()}. This allows for + * visiting patterns and possibly enable serviceability features. + *

+ * Scannable is also a useful tool for the advanced user eager to learn which kind + * of state we usually manage in the package-scope schedulers or operators + * implementations. + * + * @author Stephane Maldini + */ +@FunctionalInterface +public interface Scannable { + + /** + * The pattern for matching words unrelated to operator name. + * Used to strip an operator name of various prefixes and suffixes. + */ + Pattern OPERATOR_NAME_UNRELATED_WORDS_PATTERN = + Pattern.compile("Parallel|Flux|Mono|Publisher|Subscriber|Fuseable|Operator|Conditional"); + + /** + * Base class for {@link Scannable} attributes, which all can define a meaningful + * default. + * + * @param the type of data associated with an attribute + */ + class Attr { + + /* IMPLEMENTATION NOTE + Note that some attributes define a object-to-T converter, which means their + private {@link #tryConvert(Object)} method can safely be used by + {@link Scannable#scan(Attr)}, making them resilient to class cast exceptions. + */ + + /** + * The direct dependent component downstream reference if any. Operators in + * Flux/Mono for instance delegate to a target Subscriber, which is going to be + * the actual chain navigated with this reference key. Subscribers are not always + * {@link Scannable}, but this attribute will convert these raw results to an + * {@link Scannable#isScanAvailable() unavailable scan} object in this case. + *

+ * A reference chain downstream can be navigated via {@link Scannable#actuals()}. + */ + public static final Attr ACTUAL = new Attr<>(null, + Scannable::from); + + /** + * Indicate that for some purposes a {@link Scannable} should be used as additional + * source of information about a contiguous {@link Scannable} in the chain. + *

+ * For example {@link Scannable#steps()} uses this to collate the + * {@link Scannable#stepName() stepName} of an assembly trace to its + * wrapped operator (the one before it in the assembly chain). + */ + public static final Attr ACTUAL_METADATA = new Attr<>(false); + + /** + * A {@link Integer} attribute implemented by components with a backlog + * capacity. It will expose current queue size or similar related to + * user-provided held data. Note that some operators and processors CAN keep + * a backlog larger than {@code Integer.MAX_VALUE}, in which case + * the {@link Attr#LARGE_BUFFERED Attr} {@literal LARGE_BUFFERED} + * should be used instead. Such operators will attempt to serve a BUFFERED + * query but will return {@link Integer#MIN_VALUE} when actual buffer size is + * oversized for int. + */ + public static final Attr BUFFERED = new Attr<>(0); + + /** + * Return an an {@link Integer} capacity when no {@link #PREFETCH} is defined or + * when an arbitrary maximum limit is applied to the backlog capacity of the + * scanned component. {@link Integer#MAX_VALUE} signal unlimited capacity. + *

+ * Note: This attribute usually resolves to a constant value. + */ + public static final Attr CAPACITY = new Attr<>(0); + + /** + * A {@link Boolean} attribute indicating whether or not a downstream component + * has interrupted consuming this scanned component, e.g., a cancelled + * subscription. Note that it differs from {@link #TERMINATED} which is + * intended for "normal" shutdown cycles. + */ + public static final Attr CANCELLED = new Attr<>(false); + + /** + * Delay_Error exposes a {@link Boolean} whether the scanned component + * actively supports error delaying if it manages a backlog instead of fast + * error-passing which might drop pending backlog. + *

+ * Note: This attribute usually resolves to a constant value. + */ + public static final Attr DELAY_ERROR = new Attr<>(false); + + /** + * a {@link Throwable} attribute which indicate an error state if the scanned + * component keeps track of it. + */ + public static final Attr ERROR = new Attr<>(null); + + /** + * Similar to {@link Attr#BUFFERED}, but reserved for operators that can hold + * a backlog of items that can grow beyond {@literal Integer.MAX_VALUE}. These + * operators will also answer to a {@link Attr#BUFFERED} query up to the point + * where their buffer is actually too large, at which point they'll return + * {@literal Integer.MIN_VALUE}, which serves as a signal that this attribute + * should be used instead. Defaults to {@literal null}. + *

+ * {@code Flux.flatMap}, {@code Flux.filterWhen} + * and {@code Flux.window} (with overlap) are known to use this attribute. + */ + public static final Attr LARGE_BUFFERED = new Attr<>(null); + + /** + * An arbitrary name given to the operator component. Defaults to {@literal null}. + */ + public static final Attr NAME = new Attr<>(null); + + /** + * Parent key exposes the direct upstream relationship of the scanned component. + * It can be a Publisher source to an operator, a Subscription to a Subscriber + * (main flow if ambiguous with inner Subscriptions like flatMap), a Scheduler to + * a Worker. These types are not always {@link Scannable}, but this attribute + * will convert such raw results to an {@link Scannable#isScanAvailable() unavailable scan} + * object in this case. + *

+ * {@link Scannable#parents()} can be used to navigate the parent chain. + */ + public static final Attr PARENT = new Attr<>(null, + Scannable::from); + + /** + * A key that links a {@link Scannable} to another {@link Scannable} it runs on. + * Usually exposes a link between an operator/subscriber and its {@link Worker} or + * {@link Scheduler}, provided these are {@link Scannable}. Will return + * {@link Attr#UNAVAILABLE_SCAN} if the supporting execution is not Scannable or + * {@link Attr#NULL_SCAN} if the operator doesn't define a specific runtime. + */ + public static final Attr RUN_ON = new Attr<>(null, Scannable::from); + + /** + * Prefetch is an {@link Integer} attribute defining the rate of processing in a + * component which has capacity to request and hold a backlog of data. It + * usually maps to a component capacity when no arbitrary {@link #CAPACITY} is + * set. {@link Integer#MAX_VALUE} signal unlimited capacity and therefore + * unbounded demand. + *

+ * Note: This attribute usually resolves to a constant value. + */ + public static final Attr PREFETCH = new Attr<>(0); + + /** + * A {@link Long} attribute exposing the current pending demand of a downstream + * component. Note that {@link Long#MAX_VALUE} indicates an unbounded (push-style) + * demand as specified in {@link org.reactivestreams.Subscription#request(long)}. + */ + public static final Attr REQUESTED_FROM_DOWNSTREAM = new Attr<>(0L); + + /** + * A {@link Boolean} attribute indicating whether or not an upstream component + * terminated this scanned component. e.g. a post onComplete/onError subscriber. + * By opposition to {@link #CANCELLED} which determines if a downstream + * component interrupted this scanned component. + */ + public static final Attr TERMINATED = new Attr<>(false); + + /** + * A {@link Stream} of {@link reactor.util.function.Tuple2} representing key/value + * pairs for tagged components. Defaults to {@literal null}. + */ + public static final Attr>> TAGS = new Attr<>(null); + + /** + * An {@link RunStyle} enum attribute indicating whether or not an operator continues to operate on the same thread. + * Each value provides a different degree of guarantee from weakest {@link RunStyle#UNKNOWN} to strongest {@link RunStyle#SYNC}. + * + * Defaults to {@link RunStyle#UNKNOWN}. + */ + public static final Attr RUN_STYLE = new Attr<>(RunStyle.UNKNOWN); + + /** + * LIFTER attribute exposes name of the lifter function. It is calculated as {@link Object#toString} of a function passed to the + * {@link reactor.core.publisher.Operators#lift} or {@link reactor.core.publisher.Operators#liftPublisher}. + * Defaults to {@literal null}. + */ + public static final Attr LIFTER = new Attr<>(null); + + /** + * An {@link Enum} enumerating the different styles an operator can run : their {@link #ordinal()} reflects the level of confidence + * in their running mode + */ + public enum RunStyle { + /** + no guarantees can be given on the running mode (default value, weakest level of guarantee) + */ + UNKNOWN, + + /** + the operator may change threads while running + */ + ASYNC, + + /** + guarantees the operator doesn't change threads (strongest level of guarantee) + */ + SYNC; + } + + /** + * Meaningful and always applicable default value for the attribute, returned + * instead of {@literal null} when a specific value hasn't been defined for a + * component. {@literal null} if no sensible generic default is available. + * + * @return the default value applicable to all components or null if none. + */ + @Nullable + public T defaultValue(){ + return defaultValue; + } + + /** + * Checks if this attribute is capable of safely converting any Object into + * a {@code T} via {@link #tryConvert(Object)} (potentially returning {@code null} + * or a Null Object for incompatible raw values). + * @return true if the attribute can safely convert any object, false if it can + * throw {@link ClassCastException} + */ + boolean isConversionSafe() { + return safeConverter != null; + } + + /** + * Attempt to convert any {@link Object} instance o into a {@code T}. By default + * unsafe attributes will just try forcing a cast, which can lead to {@link ClassCastException}. + * However, attributes for which {@link #isConversionSafe()} returns true are + * required to not throw an exception (but rather return {@code null} or a Null + * Object). + * + * @param o the instance to attempt conversion on + * @return the converted instance + */ + @Nullable + T tryConvert(@Nullable Object o) { + if (o == null) { + return null; + } + if (safeConverter == null) { + @SuppressWarnings("unchecked") T t = (T) o; + return t; + } + return safeConverter.apply(o); + } + + final T defaultValue; + final Function safeConverter; + + protected Attr(@Nullable T defaultValue){ + this(defaultValue, null); + } + + protected Attr(@Nullable T defaultValue, + @Nullable Function safeConverter) { + this.defaultValue = defaultValue; + this.safeConverter = safeConverter; + } + + /** + * A constant that represents {@link Scannable} returned via {@link #from(Object)} + * when the passed non-null reference is not a {@link Scannable} + */ + static final Scannable UNAVAILABLE_SCAN = new Scannable() { + @Override + public Object scanUnsafe(Attr key) { + return null; + } + + @Override + public boolean isScanAvailable() { + return false; + } + + @Override + public String toString() { + return "UNAVAILABLE_SCAN"; + } + + @Override + public String stepName() { + return "UNAVAILABLE_SCAN"; + } + }; + + /** + * A constant that represents {@link Scannable} returned via {@link #from(Object)} + * when the passed reference is null + */ + static final Scannable NULL_SCAN = new Scannable() { + @Override + public Object scanUnsafe(Attr key) { + return null; + } + + @Override + public boolean isScanAvailable() { + return false; + } + + @Override + public String toString() { + return "NULL_SCAN"; + } + + @Override + public String stepName() { + return "NULL_SCAN"; + } + }; + + static Stream recurse(Scannable _s, + Attr key){ + Scannable s = Scannable.from(_s.scan(key)); + if(!s.isScanAvailable()) { + return Stream.empty(); + } + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator() { + Scannable c = s; + + @Override + public boolean hasNext() { + return c != null && c.isScanAvailable(); + } + + @Override + public Scannable next() { + Scannable _c = c; + c = Scannable.from(c.scan(key)); + return _c; + } + }, 0),false); + } + } + + /** + * Attempt to cast the Object to a {@link Scannable}. Return {@link Attr#NULL_SCAN} if + * the value is null, or {@link Attr#UNAVAILABLE_SCAN} if the value is not a {@link Scannable}. + * Both are constant {@link Scannable} that return false on {@link Scannable#isScanAvailable}. + * + * @param o a reference to cast + * + * @return the casted {@link Scannable}, or one of two default {@link Scannable} instances + * that aren't actually scannable (one for nulls, one for non-scannable references) + */ + static Scannable from(@Nullable Object o) { + if (o == null) { + return Attr.NULL_SCAN; + } + if (o instanceof Scannable) { + return ((Scannable) o); + } + return Attr.UNAVAILABLE_SCAN; + } + + /** + * Return a {@link Stream} navigating the {@link org.reactivestreams.Subscriber} + * chain (downward). The current {@link Scannable} is not included. + * + * @return a {@link Stream} navigating the {@link org.reactivestreams.Subscriber} + * chain (downward, current {@link Scannable} not included). + */ + default Stream actuals() { + return Attr.recurse(this, Attr.ACTUAL); + } + + /** + * Return a {@link Stream} of referenced inners (flatmap, multicast etc) + * + * @return a {@link Stream} of referenced inners (flatmap, multicast etc) + */ + default Stream inners() { + return Stream.empty(); + } + + /** + * Return true whether the component is available for {@link #scan(Attr)} resolution. + * + * @return true whether the component is available for {@link #scan(Attr)} resolution. + */ + default boolean isScanAvailable(){ + return true; + } + + /** + * Check this {@link Scannable} and its {@link #parents()} for a user-defined name and + * return the first one that is reachable, or default to this {@link Scannable} + * {@link #stepName()} if none. + * + * @return the name of the first parent that has one defined (including this scannable) + */ + default String name() { + String thisName = this.scan(Attr.NAME); + if (thisName != null) { + return thisName; + } + + return parents() + .map(s -> s.scan(Attr.NAME)) + .filter(Objects::nonNull) + .findFirst() + .orElse(stepName()); + } + + /** + * Return a meaningful {@link String} representation of this {@link Scannable} in + * its chain of {@link #parents()} and {@link #actuals()}. + */ + default String stepName() { + /* + * Strip an operator name of various prefixes and suffixes. + * @param name the operator name, usually simpleClassName or fully-qualified classname. + * @return the stripped operator name + */ + String name = getClass().getName(); + int innerClassIndex = name.indexOf('$'); + if (innerClassIndex != -1) { + name = name.substring(0, innerClassIndex); + } + int stripPackageIndex = name.lastIndexOf('.'); + if (stripPackageIndex != -1) { + name = name.substring(stripPackageIndex + 1); + } + String stripped = OPERATOR_NAME_UNRELATED_WORDS_PATTERN + .matcher(name) + .replaceAll(""); + + if (!stripped.isEmpty()) { + return stripped.substring(0, 1).toLowerCase() + stripped.substring(1); + } + return stripped; + } + + /** + * List the step names in the chain of {@link Scannable} (including the current element), + * in their assembly order. This traverses the chain of {@link Scannable} both upstream + * ({@link #parents()}) and downstream ({@link #actuals()}). + *

    + *
  1. if the current Scannable is a {@link Subscriber}, the chain can reach down to + * the final subscriber, provided it is {@link Scannable} (eg. lambda subscriber)
  2. + *
  3. if it is an operator the chain can reach up to the source, if it is a Reactor + * source (that is {@link Scannable}).
  4. + *
+ * + * @return a {@link Stream} of {@link #stepName()} for each discovered step in the {@link Scannable} chain + */ + default Stream steps() { + List chain = new ArrayList<>(); + chain.addAll(parents().collect(Collectors.toList())); + Collections.reverse(chain); + chain.add(this); + chain.addAll(actuals().collect(Collectors.toList())); + + List chainNames = new ArrayList<>(chain.size()); + for (int i = 0; i < chain.size(); i++) { + Scannable step = chain.get(i); + Scannable stepAfter = null; + if (i < chain.size() - 1) { + stepAfter = chain.get(i + 1); + } + //noinspection ConstantConditions + if (stepAfter != null && stepAfter.scan(Attr.ACTUAL_METADATA)) { + chainNames.add(stepAfter.stepName()); + i++; + } + else { + chainNames.add(step.stepName()); + } + } + + return chainNames.stream(); + } + + /** + * Return a {@link Stream} navigating the {@link org.reactivestreams.Subscription} + * chain (upward). The current {@link Scannable} is not included. + * + * @return a {@link Stream} navigating the {@link org.reactivestreams.Subscription} + * chain (upward, current {@link Scannable} not included). + */ + default Stream parents() { + return Attr.recurse(this, Attr.PARENT); + } + + /** + * This method is used internally by components to define their key-value mappings + * in a single place. Although it is ignoring the generic type of the {@link Attr} key, + * implementors should take care to return values of the correct type, and return + * {@literal null} if no specific value is available. + *

+ * For public consumption of attributes, prefer using {@link #scan(Attr)}, which will + * return a typed value and fall back to the key's default if the component didn't + * define any mapping. + * + * @param key a {@link Attr} to resolve for the component. + * @return the value associated to the key for that specific component, or null if none. + */ + @Nullable + Object scanUnsafe(Attr key); + + /** + * Introspect a component's specific state {@link Attr attribute}, returning an + * associated value specific to that component, or the default value associated with + * the key, or null if the attribute doesn't make sense for that particular component + * and has no sensible default. + * + * @param key a {@link Attr} to resolve for the component. + * @return a value associated to the key or null if unmatched or unresolved + * + */ + @Nullable + default T scan(Attr key) { + //note tryConvert will just plain cast most of the time + //except e.g. for Attr + T value = key.tryConvert(scanUnsafe(key)); + if (value == null) + return key.defaultValue(); + return value; + } + + /** + * Introspect a component's specific state {@link Attr attribute}. If there's no + * specific value in the component for that key, fall back to returning the + * provided non null default. + * + * @param key a {@link Attr} to resolve for the component. + * @param defaultValue a fallback value if key resolve to {@literal null} + * + * @return a value associated to the key or the provided default if unmatched or unresolved + */ + default T scanOrDefault(Attr key, T defaultValue) { + T v; + //note tryConvert will just plain cast most of the time + //except e.g. for Attr + v = key.tryConvert(scanUnsafe(key)); + + if (v == null) { + return Objects.requireNonNull(defaultValue, "defaultValue"); + } + return v; + } + + /** + * Visit this {@link Scannable} and its {@link #parents()} and stream all the + * observed tags + * + * @return the stream of tags for this {@link Scannable} and its parents + */ + default Stream> tags() { + Stream> parentTags = + parents().flatMap(s -> s.scan(Attr.TAGS)); + + Stream> thisTags = scan(Attr.TAGS); + + if (thisTags == null) { + return parentTags; + } + + return Stream.concat( + thisTags, + parentTags + ); + } + +} Index: 3rdParty_sources/reactor/reactor/core/package-info.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/package-info.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/package-info.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +/** + * Core components of the framework supporting extensions to the Reactive Stream + * programming model. + * + */ +@NonNullApi +package reactor.core; + +import reactor.util.annotation.NonNullApi; \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/BaseSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/BaseSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/BaseSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2016-2021 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.AtomicReferenceFieldUpdater; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.util.context.Context; + +/** + * A simple base class for a {@link Subscriber} implementation that lets the user + * perform a {@link #request(long)} and {@link #cancel()} on it directly. As the targeted + * use case is to manually handle requests, the {@link #hookOnSubscribe(Subscription)} and + * {@link #hookOnNext(Object)} hooks are expected to be implemented, but they nonetheless + * default to an unbounded request at subscription time. If you need to define a {@link Context} + * for this {@link BaseSubscriber}, simply override its {@link #currentContext()} method. + *

+ * Override the other optional hooks {@link #hookOnComplete()}, + * {@link #hookOnError(Throwable)} and {@link #hookOnCancel()} + * to customize the base behavior. You also have a termination hook, + * {@link #hookFinally(SignalType)}. + *

+ * Most of the time, exceptions triggered inside hooks are propagated to + * {@link #onError(Throwable)} (unless there is a fatal exception). The class is in the + * {@code reactor.core.publisher} package, as this subscriber is tied to a single + * {@link org.reactivestreams.Publisher}. + * + * @author Simon Baslé + */ +public abstract class BaseSubscriber implements CoreSubscriber, Subscription, + Disposable { + + volatile Subscription subscription; + + static AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(BaseSubscriber.class, Subscription.class, "subscription"); + + /** + * Return current {@link Subscription} + * @return current {@link Subscription} + */ + protected Subscription upstream() { + return subscription; + } + + @Override + public boolean isDisposed() { + return subscription == Operators.cancelledSubscription(); + } + + /** + * {@link Disposable#dispose() Dispose} the {@link Subscription} by + * {@link Subscription#cancel() cancelling} it. + */ + @Override + public void dispose() { + cancel(); + } + + /** + * Hook for further processing of onSubscribe's Subscription. Implement this method + * to call {@link #request(long)} as an initial request. Values other than the + * unbounded {@code Long.MAX_VALUE} imply that you'll also call request in + * {@link #hookOnNext(Object)}. + *

Defaults to request unbounded Long.MAX_VALUE as in {@link #requestUnbounded()} + * + * @param subscription the subscription to optionally process + */ + protected void hookOnSubscribe(Subscription subscription){ + subscription.request(Long.MAX_VALUE); + } + + /** + * Hook for processing of onNext values. You can call {@link #request(long)} here + * to further request data from the source {@link org.reactivestreams.Publisher} if + * the {@link #hookOnSubscribe(Subscription) initial request} wasn't unbounded. + *

Defaults to doing nothing. + * + * @param value the emitted value to process + */ + protected void hookOnNext(T value){ + // NO-OP + } + + /** + * Optional hook for completion processing. Defaults to doing nothing. + */ + protected void hookOnComplete() { + // NO-OP + } + + /** + * Optional hook for error processing. Default is to call + * {@link Exceptions#errorCallbackNotImplemented(Throwable)}. + * + * @param throwable the error to process + */ + protected void hookOnError(Throwable throwable) { + throw Exceptions.errorCallbackNotImplemented(throwable); + } + + /** + * Optional hook executed when the subscription is cancelled by calling this + * Subscriber's {@link #cancel()} method. Defaults to doing nothing. + */ + protected void hookOnCancel() { + //NO-OP + } + + /** + * Optional hook executed after any of the termination events (onError, onComplete, + * 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)}. + * + * @param type the type of termination event that triggered the hook + * ({@link SignalType#ON_ERROR}, {@link SignalType#ON_COMPLETE} or + * {@link SignalType#CANCEL}) + */ + protected void hookFinally(SignalType type) { + //NO-OP + } + + @Override + public final void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + try { + hookOnSubscribe(s); + } + catch (Throwable throwable) { + onError(Operators.onOperatorError(s, throwable, currentContext())); + } + } + } + + @Override + public final void onNext(T value) { + Objects.requireNonNull(value, "onNext"); + try { + hookOnNext(value); + } + catch (Throwable throwable) { + onError(Operators.onOperatorError(subscription, throwable, value, currentContext())); + } + } + + @Override + public final void onError(Throwable t) { + Objects.requireNonNull(t, "onError"); + + if (S.getAndSet(this, Operators.cancelledSubscription()) == Operators + .cancelledSubscription()) { + //already cancelled concurrently + Operators.onErrorDropped(t, currentContext()); + return; + } + + + try { + hookOnError(t); + } + catch (Throwable e) { + e = Exceptions.addSuppressed(e, t); + Operators.onErrorDropped(e, currentContext()); + } + finally { + safeHookFinally(SignalType.ON_ERROR); + } + } + + @Override + public final void onComplete() { + if (S.getAndSet(this, Operators.cancelledSubscription()) != Operators + .cancelledSubscription()) { + //we're sure it has not been concurrently cancelled + try { + hookOnComplete(); + } + catch (Throwable throwable) { + //onError itself will short-circuit due to the CancelledSubscription being set above + hookOnError(Operators.onOperatorError(throwable, currentContext())); + } + finally { + safeHookFinally(SignalType.ON_COMPLETE); + } + } + } + + @Override + public final void request(long n) { + if (Operators.validate(n)) { + Subscription s = this.subscription; + if (s != null) { + s.request(n); + } + } + } + + /** + * {@link #request(long) Request} an unbounded amount. + */ + public final void requestUnbounded() { + request(Long.MAX_VALUE); + } + + @Override + public final void cancel() { + if (Operators.terminate(S, this)) { + try { + hookOnCancel(); + } + catch (Throwable throwable) { + hookOnError(Operators.onOperatorError(subscription, throwable, currentContext())); + } + finally { + safeHookFinally(SignalType.CANCEL); + } + } + } + + void safeHookFinally(SignalType type) { + try { + hookFinally(type); + } + catch (Throwable finallyFailure) { + Operators.onErrorDropped(finallyFailure, currentContext()); + } + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingFirstSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingFirstSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingFirstSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Blocks until the upstream signals its first value or completes. + * + * @param the value type + * @see https://github.com/reactor/reactive-streams-commons + */ +final class BlockingFirstSubscriber extends BlockingSingleSubscriber { + + @Override + public void onNext(T t) { + if (value == null) { + value = t; + dispose(); + countDown(); + } + } + + @Override + public void onError(Throwable t) { + if (value == null) { + error = t; + } + countDown(); + } + + @Override + public String stepName() { + return "blockFirst"; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingIterable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingIterable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingIterable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Queue; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.scheduler.Schedulers; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * An iterable that consumes a Publisher in a blocking fashion. + *

+ *

It also implements methods to stream the contents via Stream + * that also supports cancellation. + * + * @param the value type + */ +final class BlockingIterable implements Iterable, Scannable { + + final CorePublisher source; + + final int batchSize; + + final Supplier> queueSupplier; + + BlockingIterable(CorePublisher source, + int batchSize, + Supplier> queueSupplier) { + if (batchSize <= 0) { + throw new IllegalArgumentException("batchSize > 0 required but it was " + batchSize); + } + this.source = Objects.requireNonNull(source, "source"); + this.batchSize = batchSize; + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return Math.min(Integer.MAX_VALUE, batchSize); //FIXME should batchSize be forced to int? + if (key == Attr.PARENT) return source; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public Iterator iterator() { + SubscriberIterator it = createIterator(); + + source.subscribe(it); + + return it; + } + + @Override + public Spliterator spliterator() { + return stream().spliterator(); // cancellation should be composed through this way + } + + /** + * @return a {@link Stream} of unknown size with onClose attached to {@link + * Subscription#cancel()} + */ + public Stream stream() { + SubscriberIterator it = createIterator(); + source.subscribe(it); + + Spliterator sp = Spliterators.spliteratorUnknownSize(it, 0); + + return StreamSupport.stream(sp, false) + .onClose(it); + } + + SubscriberIterator createIterator() { + Queue q; + + try { + q = Objects.requireNonNull(queueSupplier.get(), + "The queueSupplier returned a null queue"); + } + catch (Throwable e) { + throw Exceptions.propagate(e); + } + + return new SubscriberIterator<>(q, batchSize); + } + + static final class SubscriberIterator + implements InnerConsumer, Iterator, Runnable { + + final Queue queue; + + final int batchSize; + + final int limit; + + final Lock lock; + + final Condition condition; + + long produced; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(SubscriberIterator.class, + Subscription.class, + "s"); + + volatile boolean done; + Throwable error; + + SubscriberIterator(Queue queue, int batchSize) { + this.queue = queue; + this.batchSize = batchSize; + this.limit = Operators.unboundedOrLimit(batchSize); + this.lock = new ReentrantLock(); + this.condition = lock.newCondition(); + } + + @Override + public Context currentContext() { + return Context.empty(); + } + + @Override + public boolean hasNext() { + if (Schedulers.isInNonBlockingThread()) { + throw new IllegalStateException("Iterating over a toIterable() / toStream() is blocking, which is not supported in thread " + Thread.currentThread().getName()); + } + for (; ; ) { + boolean d = done; + boolean empty = queue.isEmpty(); + if (d) { + Throwable e = error; + if (e != null) { + throw Exceptions.propagate(e); + } + else if (empty) { + return false; + } + } + if (empty) { + lock.lock(); + try { + while (!done && queue.isEmpty()) { + condition.await(); + } + } + catch (InterruptedException ex) { + run(); + throw Exceptions.propagate(ex); + } + finally { + lock.unlock(); + } + } + else { + return true; + } + } + } + + @Override + public T next() { + // hasNext will start by checking the thread, so `next()` would be rejected on a NONBLOCKING thread + if (hasNext()) { + T v = queue.poll(); + + if (v == null) { + run(); + + throw new IllegalStateException("Queue is empty: Expected one element to be available from the Reactive Streams source."); + } + + long p = produced + 1; + if (p == limit) { + produced = 0; + s.request(p); + } + else { + produced = p; + } + + return v; + } + throw new NoSuchElementException(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Operators.unboundedOrPrefetch(batchSize)); + } + } + + @Override + public void onNext(T t) { + if (!queue.offer(t)) { + Operators.terminate(S, this); + + onError(Operators.onOperatorError(null, + Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), + t, currentContext())); + } + else { + signalConsumer(); + } + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + signalConsumer(); + } + + @Override + public void onComplete() { + done = true; + signalConsumer(); + } + + void signalConsumer() { + lock.lock(); + try { + condition.signalAll(); + } + finally { + lock.unlock(); + } + } + + @Override + public void run() { + Operators.terminate(S, this); + signalConsumer(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PREFETCH) return batchSize; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingLastSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingLastSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingLastSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2015-2021 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; + +/** + * Blocks until the upstream signals its last value or completes. + * + * @param the value type + * @see https://github.com/reactor/reactive-streams-commons + */ +final class BlockingLastSubscriber extends BlockingSingleSubscriber { + + @Override + public void onNext(T t) { + value = t; + } + + @Override + public void onError(Throwable t) { + value = null; + error = t; + countDown(); + } + + @Override + public String stepName() { + return "blockLast"; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingMonoSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingMonoSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingMonoSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * 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 + * in onNext. + * + * @param the value type + * @see https://github.com/reactor/reactive-streams-commons + */ +final class BlockingMonoSubscriber extends BlockingSingleSubscriber { + + @Override + public void onNext(T t) { + if (value == null) { + value = t; + countDown(); + } + } + + @Override + public void onError(Throwable t) { + if (value == null) { + error = t; + } + countDown(); + } + + @Override + public String stepName() { + return "block"; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingOptionalMonoSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingOptionalMonoSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingOptionalMonoSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2016-2021 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 java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.Subscription; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.scheduler.Schedulers; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Blocks assuming the upstream is a Mono, until it signals its value or completes. + * Similar to {@link BlockingSingleSubscriber}, except blockGet methods return an {@link Optional} + * and thus aren't nullable. + * + * @param the value type + * @see https://github.com/reactor/reactive-streams-commons + */ +final class BlockingOptionalMonoSubscriber extends CountDownLatch + implements InnerConsumer, Disposable { + + T value; + Throwable error; + + Subscription s; + + volatile boolean cancelled; + + BlockingOptionalMonoSubscriber() { + super(1); + } + + @Override + public void onNext(T t) { + if (value == null) { + value = t; + countDown(); + } + } + + @Override + public void onError(Throwable t) { + if (value == null) { + error = t; + } + countDown(); + } + + @Override + public final void onSubscribe(Subscription s) { + this.s = s; + if (!cancelled) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public final void onComplete() { + countDown(); + } + + @Override + public Context currentContext() { + return Context.empty(); + } + + @Override + public final void dispose() { + cancelled = true; + Subscription s = this.s; + if (s != null) { + this.s = null; + s.cancel(); + } + } + + /** + * Block until the first value arrives or the source completes empty, and return it + * as an {@link Optional}. Rethrow any exception. + * + * @return an Optional representing the first value (or empty Optional if the source is empty) + */ + final Optional blockingGet() { + if (Schedulers.isInNonBlockingThread()) { + throw new IllegalStateException("blockOptional() is blocking, which is not supported in thread " + Thread.currentThread().getName()); + } + if (getCount() != 0) { + try { + await(); + } + catch (InterruptedException ex) { + dispose(); + 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")); + throw re; + } + } + + Throwable e = error; + if (e != null) { + RuntimeException re = Exceptions.propagate(e); + //this is ok, as re is always a new non-singleton instance + re.addSuppressed(new Exception("#block terminated with an error")); + throw re; + } + return Optional.ofNullable(value); + } + + /** + * Block until the first value arrives or the source completes empty, and return it + * as an {@link Optional}. Rethrow any exception. + * + * @param timeout the timeout to wait + * @param unit the time unit + * + * @return an Optional representing the first value (or empty Optional if the source is empty) + */ + final Optional blockingGet(long timeout, TimeUnit unit) { + if (Schedulers.isInNonBlockingThread()) { + throw new IllegalStateException("blockOptional() is blocking, which is not supported in thread " + Thread.currentThread().getName()); + } + if (getCount() != 0) { + try { + if (!await(timeout, unit)) { + dispose(); + throw new IllegalStateException("Timeout on blocking read for " + timeout + " " + unit); + } + } + catch (InterruptedException ex) { + dispose(); + 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")); + throw re; + } + } + + Throwable e = error; + if (e != null) { + RuntimeException re = Exceptions.propagate(e); + //this is ok, as re is always a new non-singleton instance + re.addSuppressed(new Exception("#block terminated with an error")); + throw re; + } + return Optional.ofNullable(value); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return getCount() == 0; + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.ERROR) return error; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public boolean isDisposed() { + return cancelled || getCount() == 0; + } + + @Override + public String stepName() { + return "blockOptional"; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/BlockingSingleSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/BlockingSingleSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/BlockingSingleSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2016-2021 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.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.Subscription; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.scheduler.Schedulers; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * @see https://github.com/reactor/reactive-streams-commons + */ +abstract class BlockingSingleSubscriber extends CountDownLatch + implements InnerConsumer, Disposable { + + T value; + Throwable error; + + Subscription s; + + volatile boolean cancelled; + + BlockingSingleSubscriber() { + super(1); + } + + @Override + public final void onSubscribe(Subscription s) { + this.s = s; + if (!cancelled) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public final void onComplete() { + countDown(); + } + + @Override + public Context currentContext() { + return Context.empty(); + } + + @Override + public final void dispose() { + cancelled = true; + Subscription s = this.s; + if (s != null) { + this.s = null; + s.cancel(); + } + } + + /** + * Block until the first value arrives and return it, otherwise + * return null for an empty source and rethrow any exception. + * + * @return the first value or null if the source is empty + */ + @Nullable + final T blockingGet() { + if (Schedulers.isInNonBlockingThread()) { + throw new IllegalStateException("block()/blockFirst()/blockLast() are blocking, which is not supported in thread " + Thread.currentThread().getName()); + } + if (getCount() != 0) { + try { + await(); + } + catch (InterruptedException ex) { + dispose(); + throw Exceptions.propagate(ex); + } + } + + Throwable e = error; + if (e != null) { + RuntimeException re = Exceptions.propagate(e); + //this is ok, as re is always a new non-singleton instance + re.addSuppressed(new Exception("#block terminated with an error")); + throw re; + } + return value; + } + + /** + * Block until the first value arrives and return it, otherwise + * return null for an empty source and rethrow any exception. + * + * @param timeout the timeout to wait + * @param unit the time unit + * + * @return the first value or null if the source is empty + */ + @Nullable + final T blockingGet(long timeout, TimeUnit unit) { + if (Schedulers.isInNonBlockingThread()) { + throw new IllegalStateException("block()/blockFirst()/blockLast() are blocking, which is not supported in thread " + Thread.currentThread().getName()); + } + if (getCount() != 0) { + try { + if (!await(timeout, unit)) { + dispose(); + throw new IllegalStateException("Timeout on blocking read for " + timeout + " " + unit); + } + } + catch (InterruptedException ex) { + dispose(); + 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")); + throw re; + } + } + + Throwable e = error; + if (e != null) { + RuntimeException re = Exceptions.propagate(e); + //this is ok, as re is always a new non-singleton instance + re.addSuppressed(new Exception("#block terminated with an error")); + throw re; + } + return value; + } + + + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return getCount() == 0; + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.ERROR) return error; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public boolean isDisposed() { + return cancelled || getCount() == 0; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/BufferOverflowStrategy.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/BufferOverflowStrategy.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/BufferOverflowStrategy.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015-2021 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; + +/** + * Strategies to deal with overflow of a buffer during + * {@link Flux#onBackpressureBuffer(int, BufferOverflowStrategy) + * backpressure buffering}. + * + * @author Simon Baslé + */ +public enum BufferOverflowStrategy { + + /** + * Propagate an {@link IllegalStateException} when the buffer is full. + */ + ERROR, + /** + * Drop the new element without propagating an error when the buffer is full. + */ + DROP_LATEST, + /** + * When the buffer is full, remove the oldest element from it and offer the + * new element at the end instead. Do not propagate an error. + */ + DROP_OLDEST + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ConnectableFlux.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ConnectableFlux.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ConnectableFlux.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2016-2021 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.function.Consumer; + +import reactor.core.Disposable; +import reactor.core.Fuseable; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; + +/** + * The abstract base class for connectable publishers that let subscribers pile up + * before they connect to their data source. + * + * @see #publish + * @see Reactive-Streams-Commons + * @param the input and output value type + */ +public abstract class ConnectableFlux extends Flux { + + /** + * Connects this {@link ConnectableFlux} to the upstream source when the first {@link org.reactivestreams.Subscriber} + * subscribes. + * + *

+ * + * + * @return a {@link Flux} that connects to the upstream source when the first {@link org.reactivestreams.Subscriber} subscribes + */ + public final Flux autoConnect() { + return autoConnect(1); + } + + /** + * Connects this {@link ConnectableFlux} to the upstream source when the specified amount of + * {@link org.reactivestreams.Subscriber} subscribes. + *

+ * Subscribing and immediately unsubscribing still contributes to the counter that + * triggers the connection. + * + *

+ * + * + * @param minSubscribers the minimum number of subscribers + * + * @return a {@link Flux} that connects to the upstream source when the given amount of Subscribers subscribed + */ + public final Flux autoConnect(int minSubscribers) { + return autoConnect(minSubscribers, NOOP_DISCONNECT); + } + + /** + * Connects this {@link ConnectableFlux} to the upstream source when the specified amount of + * {@link org.reactivestreams.Subscriber} subscribes and calls the supplied consumer with a {@link Disposable} + * that allows disconnecting. + *

+ * + * + * @param minSubscribers the minimum number of subscribers + * @param cancelSupport the consumer that will receive the {@link Disposable} that allows disconnecting + * @return a {@link Flux} that connects to the upstream source when the given amount of subscribers subscribed + */ + public final Flux autoConnect(int minSubscribers, Consumer cancelSupport) { + if (minSubscribers == 0) { + connect(cancelSupport); + return this; + } + if(this instanceof Fuseable){ + return onAssembly(new FluxAutoConnectFuseable<>(this, + minSubscribers, + cancelSupport)); + } + return onAssembly(new FluxAutoConnect<>(this, minSubscribers, + cancelSupport)); + } + + /** + * Connect this {@link ConnectableFlux} to its source and return a {@link Disposable} that + * can be used for disconnecting. + * @return the {@link Disposable} that allows disconnecting the connection after. + */ + public final Disposable connect() { + final Disposable[] out = { null }; + connect(r -> out[0] = r); + return out[0]; + } + + /** + * Connects this {@link ConnectableFlux} to its source and sends a {@link Disposable} to a callback that + * can be used for disconnecting. + * + *

The call should be idempotent in respect of connecting the first + * and subsequent times. In addition, the disconnection should be tied + * to a particular connection (so two different connections can't disconnect the + * other). + * + * @param cancelSupport the callback is called with a Disposable instance that can + * be called to disconnect the source, even synchronously. + */ + public abstract void connect(Consumer cancelSupport); + + @Override + public final ConnectableFlux hide() { + return new ConnectableFluxHide<>(this); + } + + /** + * Connects to the upstream source when the first {@link org.reactivestreams.Subscriber} subscribes and disconnects + * when all Subscribers cancelled or the upstream source completed. + * + *

+ * + * + * @return a reference counting {@link Flux} + */ + public final Flux refCount() { + return refCount(1); + } + + /** + * Connects to the upstream source when the given number of {@link org.reactivestreams.Subscriber} subscribes and disconnects + * when all Subscribers cancelled or the upstream source completed. + * + *

+ * + * + * @param minSubscribers the number of subscribers expected to subscribe before connection + * + * @return a reference counting {@link Flux} + */ + public final Flux refCount(int minSubscribers) { + return onAssembly(new FluxRefCount<>(this, minSubscribers)); + } + + /** + * Connects to the upstream source when the given number of {@link org.reactivestreams.Subscriber} subscribes. + * Disconnection can happen in two scenarios: when the upstream source completes (or errors) then + * there is an immediate disconnection. However, when all subscribers have cancelled, + * a deferred disconnection is scheduled. If any new subscriber comes + * in during the {@code gracePeriod} that follows, the disconnection is cancelled. + * + *

+ * + * + * @param minSubscribers the number of subscribers expected to subscribe before connection + * @param gracePeriod the {@link Duration} for which to wait for new subscribers before actually + * disconnecting when all subscribers have cancelled. + * + * @return a reference counting {@link Flux} with a grace period for disconnection + */ + public final Flux refCount(int minSubscribers, Duration gracePeriod) { + return refCount(minSubscribers, gracePeriod, Schedulers.parallel()); + } + + /** + * Connects to the upstream source when the given number of {@link org.reactivestreams.Subscriber} subscribes. + * Disconnection can happen in two scenarios: when the upstream source completes (or errors) then + * there is an immediate disconnection. However, when all subscribers have cancelled, + * a deferred disconnection is scheduled. If any new subscriber comes + * in during the {@code gracePeriod} that follows, the disconnection is cancelled. + * + *

+ * + * + * @param minSubscribers the number of subscribers expected to subscribe before connection + * @param gracePeriod the {@link Duration} for which to wait for new subscribers before actually + * disconnecting when all subscribers have cancelled. + * @param scheduler the {@link Scheduler} on which to run timeouts + * + * @return a reference counting {@link Flux} with a grace period for disconnection + */ + public final Flux refCount(int minSubscribers, Duration gracePeriod, Scheduler scheduler) { + return onAssembly(new FluxRefCountGrace<>(this, minSubscribers, gracePeriod, scheduler)); + } + + static final Consumer NOOP_DISCONNECT = runnable -> { + + }; +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ConnectableFluxHide.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ConnectableFluxHide.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ConnectableFluxHide.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Hide a {@link ConnectableFlux} from fusion optimizations while keeping the {@link ConnectableFlux} + * specific API visible. + * + * @author Simon Baslé + */ +final class ConnectableFluxHide extends InternalConnectableFluxOperator implements Scannable { + + ConnectableFluxHide(ConnectableFlux source) { + super(source); + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + @Nullable + 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; + + return null; + } + + @Override + public final CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public void connect(Consumer cancelSupport) { + source.connect(cancelSupport); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ConnectableFluxOnAssembly.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ConnectableFluxOnAssembly.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ConnectableFluxOnAssembly.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; +import reactor.util.annotation.Nullable; + +/** + * Captures the current stacktrace when this connectable publisher is created and + * makes it available/visible for debugging purposes from + * the inner Subscriber. + *

+ * Note that getting a stacktrace is a costly operation. + *

+ * The operator sanitizes the stacktrace and removes noisy entries such as: + *

    + *
  • java.lang.Thread entries
  • + *
  • method references with source line of 1 (bridge methods)
  • + *
  • Tomcat worker thread entries
  • + *
  • JUnit setup
  • + *
+ * + * @param the value type passing through + * @see https://github.com/reactor/reactive-streams-commons + */ +final class ConnectableFluxOnAssembly extends InternalConnectableFluxOperator implements + Fuseable, AssemblyOp, + Scannable { + + final AssemblySnapshot stacktrace; + + ConnectableFluxOnAssembly(ConnectableFlux source, AssemblySnapshot stacktrace) { + super(source); + this.stacktrace = stacktrace; + } + + @Override + public final CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return FluxOnAssembly.wrapSubscriber(actual, source, this, stacktrace); + } + + @Override + public void connect(Consumer cancelSupport) { + source.connect(cancelSupport); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + if (key == Attr.ACTUAL_METADATA) return !stacktrace.isCheckpoint; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public String stepName() { + return stacktrace.operatorAssemblyInformation(); + } + + @Override + public String toString() { + return stacktrace.operatorAssemblyInformation(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ConnectableLift.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ConnectableLift.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ConnectableLift.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * @author Simon Baslé + */ +final class ConnectableLift extends InternalConnectableFluxOperator implements Scannable { + + final Operators.LiftFunction liftFunction; + + ConnectableLift(ConnectableFlux p, + Operators.LiftFunction liftFunction) { + super(Objects.requireNonNull(p, "source")); + this.liftFunction = liftFunction; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public void connect(Consumer cancelSupport) { + this.source.connect(cancelSupport); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return source.getPrefetch(); + if (key == Attr.PARENT) return source; + if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); + if (key == Attr.LIFTER) return liftFunction.name; + return null; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return super.stepName(); + } + + @Override + public final CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CoreSubscriber input = + liftFunction.lifter.apply(source, actual); + + Objects.requireNonNull(input, "Lifted subscriber MUST NOT be null"); + + return input; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ConnectableLiftFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ConnectableLiftFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ConnectableLiftFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * @author Simon Baslé + */ +final class ConnectableLiftFuseable extends InternalConnectableFluxOperator + implements Scannable, Fuseable { + + final Operators.LiftFunction liftFunction; + + ConnectableLiftFuseable(ConnectableFlux p, + Operators.LiftFunction liftFunction) { + super(Objects.requireNonNull(p, "source")); + this.liftFunction = liftFunction; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public void connect(Consumer cancelSupport) { + this.source.connect(cancelSupport); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return source.getPrefetch(); + if (key == Attr.PARENT) return source; + if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); + if (key == Attr.LIFTER) return liftFunction.name; + return null; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return super.stepName(); + } + + @Override + public final CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CoreSubscriber input = + liftFunction.lifter.apply(source, actual); + + Objects.requireNonNull(input, "Lifted subscriber MUST NOT be null"); + + 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 + return input; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ContextHolder.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ContextHolder.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ContextHolder.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015-2021 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.context.Context; + +interface ContextHolder { + + /** + * Request a {@link Context} from dependent components which can include downstream + * operators during subscribing or a terminal {@link org.reactivestreams.Subscriber}. + * + * @return a resolved context + */ + Context currentContext(); + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ContextTrackingFunctionWrapper.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ContextTrackingFunctionWrapper.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ContextTrackingFunctionWrapper.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019-2021 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.Function; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.FluxContextWrite.ContextWriteSubscriber; +import reactor.util.context.Context; + +/** + * This {@link Function} wrapper is used by operators like {@link Flux#transform(Function)} + * to implement the context loss detection. + * + */ +class ContextTrackingFunctionWrapper implements Function, CorePublisher> { + + static final String CONTEXT_MARKER_PREFIX = "reactor.core.context.marker."; + + final Function, ? extends Publisher> transformer; + + final String marker; + + ContextTrackingFunctionWrapper(Function, ? extends Publisher> transformer) { + this(transformer, transformer.toString()); + } + + ContextTrackingFunctionWrapper( + Function, ? extends Publisher> transformer, + String marker + ) { + this.transformer = transformer; + this.marker = marker; + } + + @Override + public CorePublisher apply(Publisher source) { + String key = CONTEXT_MARKER_PREFIX + System.identityHashCode(source); + + // Wrap source with a logic that will check whether the key is still there and remove it + source = Operators.liftPublisher((p, actual) -> { + Context ctx = actual.currentContext(); + + if (!ctx.hasKey(key)) { + throw new IllegalStateException("Context loss after applying " + marker); + } + + Context newContext = ctx.delete(key); + return new ContextWriteSubscriber<>(actual, newContext); + }).apply(source); + + Publisher result = transformer.apply(source); + + // It is okay to return `CorePublisher` here since `transform` will use `from()` anyways + return new CorePublisher() { + @Override + public void subscribe(CoreSubscriber actual) { + Context ctx = actual.currentContext().put(key, true); + CoreSubscriber subscriber = new ContextWriteSubscriber<>(actual, ctx); + + if (result instanceof CorePublisher) { + ((CorePublisher) result).subscribe(subscriber); + } + else { + result.subscribe(subscriber); + } + } + + @Override + public void subscribe(Subscriber subscriber) { + subscribe(Operators.toCoreSubscriber(subscriber)); + } + }; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/DelegateProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/DelegateProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/DelegateProcessor.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016-2021 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.stream.Stream; + +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; + +/** + * @author Stephane Maldini + */ +@Deprecated +final class DelegateProcessor extends FluxProcessor { + + final Publisher downstream; + final Subscriber upstream; + + DelegateProcessor(Publisher downstream, + Subscriber upstream) { + this.downstream = Objects.requireNonNull(downstream, "Downstream must not be null"); + this.upstream = Objects.requireNonNull(upstream, "Upstream must not be null"); + } + + @Override + public Context currentContext() { + if(upstream instanceof CoreSubscriber){ + return ((CoreSubscriber)upstream).currentContext(); + } + return Context.empty(); + } + + @Override + public void onComplete() { + upstream.onComplete(); + } + + @Override + public void onError(Throwable t) { + upstream.onError(t); + } + + @Override + public void onNext(IN in) { + upstream.onNext(in); + } + + @Override + public void onSubscribe(Subscription s) { + upstream.onSubscribe(s); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Objects.requireNonNull(actual, "subscribe"); + downstream.subscribe(actual); + } + + @Override + @SuppressWarnings("unchecked") + public boolean isSerialized() { + return upstream instanceof SerializedSubscriber || + (upstream instanceof FluxProcessor && + ((FluxProcessor)upstream).isSerialized()); + } + + @Override + public Stream inners() { + //noinspection ConstantConditions + return Scannable.from(upstream) + .inners(); + } + + @Override + public int getBufferSize() { + //noinspection ConstantConditions + return Scannable.from(upstream) + .scanOrDefault(Attr.CAPACITY, super.getBufferSize()); + } + + @Override + @Nullable + public Throwable getError() { + //noinspection ConstantConditions + return Scannable.from(upstream) + .scanOrDefault(Attr.ERROR, super.getError()); + } + + @Override + public boolean isTerminated() { + //noinspection ConstantConditions + return Scannable.from(upstream) + .scanOrDefault(Attr.TERMINATED, super.isTerminated()); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return downstream; + } + //noinspection ConstantConditions + return Scannable.from(upstream) + .scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/DirectInnerContainer.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/DirectInnerContainer.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/DirectInnerContainer.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * A package-private interface allowing to mutualize logic between {@link DirectProcessor} + * and {@link SinkManyBestEffort}. + * + * @author Simon Baslé + */ +interface DirectInnerContainer { + + /** + * Add a new {@link SinkManyBestEffort.DirectInner} to this publisher. + * + * @param s the new {@link SinkManyBestEffort.DirectInner} to add + * + * @return {@code true} if the inner could be added, {@code false} if the publisher cannot accept new subscribers + */ + boolean add(SinkManyBestEffort.DirectInner s); + + /** + * Remove an {@link SinkManyBestEffort.DirectInner} from this publisher. Does nothing if the inner is not currently managed + * by the publisher. + * + * @param s the {@link SinkManyBestEffort.DirectInner} to remove + */ + void remove(SinkManyBestEffort.DirectInner s); +} Index: 3rdParty_sources/reactor/reactor/core/publisher/DirectProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/DirectProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/DirectProcessor.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2016-2021 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.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.publisher.SinkManyBestEffort.DirectInner; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Dispatches onNext, onError and onComplete signals to zero-to-many Subscribers. + * Please note, that along with multiple consumers, current implementation of + * DirectProcessor supports multiple producers. However, all producers must produce + * messages on the same Thread, otherwise + * Reactive Streams Spec contract is + * violated. + *

+ * + *

+ * + *
+ *
+ * + *

+ * Note: DirectProcessor does not coordinate backpressure between its + * Subscribers and the upstream, but consumes its upstream in an + * unbounded manner. + * In the case where a downstream Subscriber is not ready to receive items (hasn't + * requested yet or enough), it will be terminated with an + * {@link IllegalStateException}. + * Hence in terms of interaction model, DirectProcessor only supports PUSH from the + * source through the processor to the Subscribers. + * + *

+ * + *

+ *

+ * + *
+ *
+ * + *

+ * Note: If there are no Subscribers, upstream items are dropped and only + * the terminal events are retained. A terminated DirectProcessor will emit the + * terminal signal to late subscribers. + * + *

+ * + *

+ *

+ * + *
+ *
+ * + *

+ * Note: The implementation ignores Subscriptions set via onSubscribe. + *

+ * + * @param the input and output value type + * @deprecated To be removed in 3.5, prefer clear cut usage of {@link Sinks}. Closest sink + * is {@link Sinks.MulticastSpec#directBestEffort() Sinks.many().multicast().directBestEffort()}, + * except it doesn't terminate overflowing downstreams. + */ +@Deprecated +public final class DirectProcessor extends FluxProcessor + implements DirectInnerContainer { + + /** + * Create a new {@link DirectProcessor} + * + * @param Type of processed signals + * + * @return a fresh processor + * @deprecated To be removed in 3.5. Closest sink is {@link Sinks.MulticastSpec#directBestEffort() Sinks.many().multicast().directBestEffort()}, + * except it doesn't terminate overflowing downstreams. + */ + @Deprecated + public static DirectProcessor create() { + return new DirectProcessor<>(); + } + + @SuppressWarnings("unchecked") + private volatile DirectInner[] subscribers = SinkManyBestEffort.EMPTY; + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdaterSUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater(DirectProcessor.class, DirectInner[].class, "subscribers"); + + Throwable error; + + DirectProcessor() { + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + @Override + public void onSubscribe(Subscription s) { + Objects.requireNonNull(s, "s"); + if (subscribers != SinkManyBestEffort.TERMINATED) { + s.request(Long.MAX_VALUE); + } + else { + s.cancel(); + } + } + + @Override + public void onComplete() { + //no particular error condition handling for onComplete + @SuppressWarnings("unused") Sinks.EmitResult emitResult = tryEmitComplete(); + } + + private void emitComplete() { + //no particular error condition handling for onComplete + @SuppressWarnings("unused") EmitResult emitResult = tryEmitComplete(); + } + + private EmitResult tryEmitComplete() { + @SuppressWarnings("unchecked") + DirectInner[] inners = SUBSCRIBERS.getAndSet(this, SinkManyBestEffort.TERMINATED); + + if (inners == SinkManyBestEffort.TERMINATED) { + return EmitResult.FAIL_TERMINATED; + } + + for (DirectInner s : inners) { + s.emitComplete(); + } + return EmitResult.OK; + } + + @Override + public void onError(Throwable throwable) { + emitError(throwable); + } + + private void emitError(Throwable error) { + Sinks.EmitResult result = tryEmitError(error); + if (result == EmitResult.FAIL_TERMINATED) { + Operators.onErrorDroppedMulticast(error, subscribers); + } + } + + private Sinks.EmitResult tryEmitError(Throwable t) { + Objects.requireNonNull(t, "t"); + + @SuppressWarnings("unchecked") + DirectInner[] inners = SUBSCRIBERS.getAndSet(this, SinkManyBestEffort.TERMINATED); + + if (inners == SinkManyBestEffort.TERMINATED) { + return EmitResult.FAIL_TERMINATED; + } + + error = t; + for (DirectInner s : inners) { + s.emitError(t); + } + return Sinks.EmitResult.OK; + } + + private void emitNext(T value) { + switch (tryEmitNext(value)) { + case FAIL_ZERO_SUBSCRIBER: + //we want to "discard" without rendering the sink terminated. + // effectively NO-OP cause there's no subscriber, so no context :( + break; + case FAIL_OVERFLOW: + Operators.onDiscard(value, currentContext()); + //the emitError will onErrorDropped if already terminated + emitError(Exceptions.failWithOverflow("Backpressure overflow during Sinks.Many#emitNext")); + break; + case FAIL_CANCELLED: + Operators.onDiscard(value, currentContext()); + break; + case FAIL_TERMINATED: + Operators.onNextDroppedMulticast(value, subscribers); + break; + case OK: + break; + default: + throw new IllegalStateException("unexpected return code"); + } + } + + @Override + public void onNext(T t) { + emitNext(t); + } + + private EmitResult tryEmitNext(T t) { + Objects.requireNonNull(t, "t"); + + DirectInner[] inners = subscribers; + + if (inners == SinkManyBestEffort.TERMINATED) { + return EmitResult.FAIL_TERMINATED; + } + if (inners == SinkManyBestEffort.EMPTY) { + return Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER; + } + + for (DirectInner s : inners) { + s.directEmitNext(t); + } + return Sinks.EmitResult.OK; + } + + @Override + protected boolean isIdentityProcessor() { + return true; + } + + @Override + public void subscribe(CoreSubscriber actual) { + Objects.requireNonNull(actual, "subscribe"); + + DirectInner p = new DirectInner<>(actual, this); + actual.onSubscribe(p); + + if (add(p)) { + if (p.isCancelled()) { + remove(p); + } + } + else { + Throwable e = error; + if (e != null) { + actual.onError(e); + } + else { + actual.onComplete(); + } + } + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public boolean isTerminated() { + return SinkManyBestEffort.TERMINATED == subscribers; + } + + @Override + public long downstreamCount() { + return subscribers.length; + } + + @Override + public boolean add(DirectInner s) { + DirectInner[] a = subscribers; + if (a == SinkManyBestEffort.TERMINATED) { + return false; + } + + synchronized (this) { + a = subscribers; + if (a == SinkManyBestEffort.TERMINATED) { + return false; + } + int len = a.length; + + @SuppressWarnings("unchecked") DirectInner[] b = new DirectInner[len + 1]; + System.arraycopy(a, 0, b, 0, len); + b[len] = s; + + subscribers = b; + + return true; + } + } + + @Override + @SuppressWarnings("unchecked") + public void remove(DirectInner s) { + DirectInner[] a = subscribers; + if (a == SinkManyBestEffort.TERMINATED || a == SinkManyBestEffort.EMPTY) { + return; + } + + synchronized (this) { + a = subscribers; + if (a == SinkManyBestEffort.TERMINATED || a == SinkManyBestEffort.EMPTY) { + return; + } + int len = a.length; + + int j = -1; + + for (int i = 0; i < len; i++) { + if (a[i] == s) { + j = i; + break; + } + } + if (j < 0) { + return; + } + if (len == 1) { + subscribers = SinkManyBestEffort.EMPTY; + return; + } + + DirectInner[] b = new DirectInner[len - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, len - j - 1); + + subscribers = b; + } + } + + @Override + public boolean hasDownstreams() { + DirectInner[] s = subscribers; + return s != SinkManyBestEffort.EMPTY && s != SinkManyBestEffort.TERMINATED; + } + + @Override + @Nullable + public Throwable getError() { + if (subscribers == SinkManyBestEffort.TERMINATED) { + return error; + } + return null; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/DrainUtils.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/DrainUtils.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/DrainUtils.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2016-2021 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.Queue; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.BooleanSupplier; + +import org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +abstract class DrainUtils { + + /** + * Indicates the source completed and the value field is ready to be emitted. + *

+ * The AtomicLong (this) holds the requested amount in bits 0..62 so there is room + * for one signal bit. This also means the standard request accounting helper method doesn't work. + */ + static final long COMPLETED_MASK = 0x8000_0000_0000_0000L; + static final long REQUESTED_MASK = 0x7FFF_FFFF_FFFF_FFFFL; + + /** + * Perform a potential post-completion request accounting. + * + * @param the output value type + * @param the field type holding the requested amount + * @param n the requested amount + * @param actual the consumer of values + * @param queue the queue holding the available values + * @param field the field updater for the requested amount + * @param instance the parent instance for the requested field + * @param isCancelled callback to detect cancellation + * @return true if the state indicates a completion state. + */ + static boolean postCompleteRequest(long n, + Subscriber actual, + Queue queue, + AtomicLongFieldUpdater field, + F instance, + BooleanSupplier isCancelled) { + + for (; ; ) { + long r = field.get(instance); + + // extract the current request amount + long r0 = r & REQUESTED_MASK; + + // preserve COMPLETED_MASK and calculate new requested amount + long u = (r & COMPLETED_MASK) | Operators.addCap(r0, n); + + if (field.compareAndSet(instance, r, u)) { + // (complete, 0) -> (complete, n) transition then replay + if (r == COMPLETED_MASK) { + + postCompleteDrain(n | COMPLETED_MASK, actual, queue, field, instance, isCancelled); + + return true; + } + // (active, r) -> (active, r + n) transition then continue with requesting from upstream + return false; + } + } + + } + + /** + * Drains the queue either in a pre- or post-complete state. + * + * @param n the requested amount + * @param actual the consumer of values + * @param queue the queue holding available values + * @param field the field updater holding the requested amount + * @param instance the parent instance of the requested field + * @param isCancelled callback to detect cancellation + * @return true if the queue was completely drained or the drain process was cancelled + */ + static boolean postCompleteDrain(long n, + Subscriber actual, + Queue queue, + AtomicLongFieldUpdater field, + F instance, + BooleanSupplier isCancelled) { + +// TODO enable fast-path +// if (n == -1 || n == Long.MAX_VALUE) { +// for (;;) { +// if (isDisposed.getAsBoolean()) { +// break; +// } +// +// T v = queue.poll(); +// +// if (v == null) { +// actual.onComplete(); +// break; +// } +// +// actual.onNext(v); +// } +// +// return true; +// } + + long e = n & COMPLETED_MASK; + + for (; ; ) { + + while (e != n) { + if (isCancelled.getAsBoolean()) { + return true; + } + + T t = queue.poll(); + + if (t == null) { + actual.onComplete(); + return true; + } + + actual.onNext(t); + e++; + } + + if (isCancelled.getAsBoolean()) { + return true; + } + + if (queue.isEmpty()) { + actual.onComplete(); + return true; + } + + n = field.get(instance); + + if (n == e) { + + n = field.addAndGet(instance, -(e & REQUESTED_MASK)); + + if ((n & REQUESTED_MASK) == 0L) { + return false; + } + + e = n & COMPLETED_MASK; + } + } + + } + + /** + * Tries draining the queue if the source just completed. + * + * @param the output value type + * @param the field type holding the requested amount + * @param actual the consumer of values + * @param queue the queue holding available values + * @param field the field updater holding the requested amount + * @param instance the parent instance of the requested field + * @param isCancelled callback to detect cancellation + */ + public static void postComplete(CoreSubscriber actual, + Queue queue, + AtomicLongFieldUpdater field, + F instance, + BooleanSupplier isCancelled) { + + if (queue.isEmpty()) { + actual.onComplete(); + return; + } + + if (postCompleteDrain(field.get(instance), actual, queue, field, instance, isCancelled)) { + return; + } + + for (; ; ) { + long r = field.get(instance); + + if ((r & COMPLETED_MASK) != 0L) { + return; + } + + long u = r | COMPLETED_MASK; + // (active, r) -> (complete, r) transition + if (field.compareAndSet(instance, r, u)) { + // if the requested amount was non-zero, drain the queue + if (r != 0L) { + postCompleteDrain(u, actual, queue, field, instance, isCancelled); + } + + return; + } + } + } + + /** + * Perform a potential post-completion request accounting. + * + * @param the output value type + * @param the field type holding the requested amount + * @param n the request amount + * @param actual the consumer of values + * @param queue the queue of available values + * @param field the field updater for the requested amount + * @param instance the parent instance of the requested field + * @param isCancelled callback to detect cancellation + * @param error if not null, the error to signal after the queue has been drained + * @return true if the state indicates a completion state. + */ + public static boolean postCompleteRequestDelayError(long n, + Subscriber actual, + Queue queue, + AtomicLongFieldUpdater field, + F instance, + BooleanSupplier isCancelled, Throwable error) { + + for (; ; ) { + long r = field.get(instance); + + // extract the current request amount + long r0 = r & REQUESTED_MASK; + + // preserve COMPLETED_MASK and calculate new requested amount + long u = (r & COMPLETED_MASK) | Operators.addCap(r0, n); + + if (field.compareAndSet(instance, r, u)) { + // (complete, 0) -> (complete, n) transition then replay + if (r == COMPLETED_MASK) { + + postCompleteDrainDelayError(n | COMPLETED_MASK, actual, queue, field, instance, isCancelled, error); + + return true; + } + // (active, r) -> (active, r + n) transition then continue with requesting from upstream + return false; + } + } + + } + + /** + * Drains the queue either in a pre- or post-complete state, delaying an + * optional error to the end of the drain operation. + * + * @param n the requested amount + * @param actual the consumer of values + * @param queue the queue holding available values + * @param field the field updater holding the requested amount + * @param instance the parent instance of the requested field + * @param isCancelled callback to detect cancellation + * @param error the delayed error + * @return true if the queue was completely drained or the drain process was cancelled + */ + static boolean postCompleteDrainDelayError(long n, + Subscriber actual, + Queue queue, + AtomicLongFieldUpdater field, + F instance, + BooleanSupplier isCancelled, + @Nullable Throwable error) { + + long e = n & COMPLETED_MASK; + + for (; ; ) { + + while (e != n) { + if (isCancelled.getAsBoolean()) { + return true; + } + + T t = queue.poll(); + + if (t == null) { + if (error == null) { + actual.onComplete(); + } else { + actual.onError(error); + } + return true; + } + + actual.onNext(t); + e++; + } + + if (isCancelled.getAsBoolean()) { + return true; + } + + if (queue.isEmpty()) { + if (error == null) { + actual.onComplete(); + } else { + actual.onError(error); + } + return true; + } + + n = field.get(instance); + + if (n == e) { + + n = field.addAndGet(instance, -(e & REQUESTED_MASK)); + + if ((n & REQUESTED_MASK) == 0L) { + return false; + } + + e = n & COMPLETED_MASK; + } + } + + } + + /** + * Tries draining the queue if the source just completed. + * + * @param the output value type + * @param the field type holding the requested amount + * @param actual the consumer of values + * @param queue the queue of available values + * @param field the field updater for the requested amount + * @param instance the parent instance of the requested field + * @param isCancelled callback to detect cancellation + * @param error if not null, the error to signal after the queue has been drained + */ + public static void postCompleteDelayError(CoreSubscriber actual, + Queue queue, + AtomicLongFieldUpdater field, + F instance, + BooleanSupplier isCancelled, + @Nullable Throwable error) { + + if (queue.isEmpty()) { + if (error == null) { + actual.onComplete(); + } else { + actual.onError(error); + } + return; + } + + if (postCompleteDrainDelayError(field.get(instance), actual, queue, field, instance, isCancelled, error)) { + return; + } + + for (; ; ) { + long r = field.get(instance); + + if ((r & COMPLETED_MASK) != 0L) { + return; + } + + long u = r | COMPLETED_MASK; + // (active, r) -> (complete, r) transition + if (field.compareAndSet(instance, r, u)) { + // if the requested amount was non-zero, drain the queue + if (r != 0L) { + postCompleteDrainDelayError(u, actual, queue, field, instance, isCancelled, error); + } + + return; + } + } + } + + DrainUtils(){} +} Index: 3rdParty_sources/reactor/reactor/core/publisher/EmitterProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/EmitterProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/EmitterProcessor.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,651 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicIntegerFieldUpdater; +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.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 message-passing Processor 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 + * @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 + * {@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 { + + @SuppressWarnings("rawtypes") + static final FluxPublish.PubSubInner[] EMPTY = new FluxPublish.PublishInner[0]; + + /** + * Create a new {@link EmitterProcessor} using {@link Queues#SMALL_BUFFER_SIZE} + * backlog size and auto-cancel. + * + * @param Type of processed signals + * + * @return a fresh processor + * @deprecated use {@link Sinks.MulticastSpec#onBackpressureBuffer() Sinks.many().multicast().onBackpressureBuffer()} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static EmitterProcessor create() { + return create(Queues.SMALL_BUFFER_SIZE, true); + } + + /** + * Create a new {@link EmitterProcessor} using {@link Queues#SMALL_BUFFER_SIZE} + * backlog size and the provided auto-cancel. + * + * @param Type of processed signals + * @param autoCancel automatically cancel + * + * @return a fresh processor + * @deprecated use {@link Sinks.MulticastSpec#onBackpressureBuffer(int, boolean) Sinks.many().multicast().onBackpressureBuffer(bufferSize, boolean)} + * using the old default of {@link Queues#SMALL_BUFFER_SIZE} for the {@code bufferSize} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static EmitterProcessor create(boolean autoCancel) { + return create(Queues.SMALL_BUFFER_SIZE, autoCancel); + } + + /** + * Create a new {@link EmitterProcessor} using the provided backlog size, with auto-cancel. + * + * @param Type of processed signals + * @param bufferSize the internal buffer size to hold signals + * + * @return a fresh processor + * @deprecated use {@link Sinks.MulticastSpec#onBackpressureBuffer(int) Sinks.many().multicast().onBackpressureBuffer(bufferSize)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static EmitterProcessor create(int bufferSize) { + return create(bufferSize, true); + } + + /** + * Create a new {@link EmitterProcessor} using the provided backlog size and auto-cancellation. + * + * @param Type of processed signals + * @param bufferSize the internal buffer size to hold signals + * @param autoCancel automatically cancel + * + * @return a fresh processor + * @deprecated use {@link Sinks.MulticastSpec#onBackpressureBuffer(int, boolean) Sinks.many().multicast().onBackpressureBuffer(bufferSize, autoCancel)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static EmitterProcessor create(int bufferSize, boolean autoCancel) { + return new EmitterProcessor<>(autoCancel, bufferSize); + } + + final int prefetch; + + final boolean autoCancel; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(EmitterProcessor.class, + Subscription.class, + "s"); + + volatile FluxPublish.PubSubInner[] subscribers; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + SUBSCRIBERS = AtomicReferenceFieldUpdater.newUpdater(EmitterProcessor.class, + FluxPublish.PubSubInner[].class, + "subscribers"); + + @SuppressWarnings("unused") + volatile int wip; + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(EmitterProcessor.class, "wip"); + + volatile Queue queue; + + int sourceMode; + + volatile boolean done; + + volatile Throwable error; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(EmitterProcessor.class, + Throwable.class, + "error"); + + EmitterProcessor(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); + } + + @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, "onError"); + if (done) { + return EmitResult.FAIL_TERMINATED; + } + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(); + return EmitResult.OK; + } + else { + return Sinks.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 Sinks.EmitResult.FAIL_TERMINATED; + } + + Objects.requireNonNull(t, "onNext"); + + 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; + } + + @Override + protected boolean isIdentityProcessor() { + return true; + } + + /** + * Return the number of parked elements in the emitter backlog. + * + * @return the number of parked elements in the emitter backlog. + */ + public int getPending() { + Queue q = queue; + return q != null ? q.size() : 0; + } + + @Override + public boolean isDisposed() { + return isTerminated() || isCancelled(); + } + + @Override + public void onSubscribe(final Subscription s) { + 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)); + } + } + + @Override + @Nullable + public Throwable getError() { + return error; + } + + /** + * @return true if all subscribers have actually been cancelled and the processor auto shut down + */ + public boolean isCancelled() { + return Operators.cancelledSubscription() == s; + } + + @Override + final public int getBufferSize() { + return prefetch; + } + + @Override + 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(); + + return super.scanUnsafe(key); + } + + 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; + } + } + + @Override + public long downstreamCount() { + return subscribers.length; + } + + static final class EmitterInner extends FluxPublish.PubSubInner { + + final EmitterProcessor parent; + + EmitterInner(CoreSubscriber actual, EmitterProcessor parent) { + super(actual); + this.parent = parent; + } + + @Override + void drainParent() { + parent.drain(); + } + + @Override + void removeAndDrainParent() { + parent.remove(this); + parent.drain(); + } + } + + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/Flux.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/Flux.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/Flux.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,10580 @@ +/* + * Copyright (c) 2016-2021 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.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Queue; +import java.util.Set; +import java.util.Spliterator; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.LongConsumer; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collector; +import java.util.stream.Stream; + +import io.micrometer.core.instrument.MeterRegistry; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.Scannable; +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; +import reactor.core.scheduler.Schedulers; +import reactor.util.Logger; +import reactor.util.Metrics; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; +import reactor.util.context.ContextView; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuple3; +import reactor.util.function.Tuple4; +import reactor.util.function.Tuple5; +import reactor.util.function.Tuple6; +import reactor.util.function.Tuple7; +import reactor.util.function.Tuple8; +import reactor.util.function.Tuples; +import reactor.util.retry.Retry; + +/** + * A Reactive Streams {@link Publisher} with rx operators that emits 0 to N elements, and then completes + * (successfully or with an error). + *

+ * The recommended way to learn about the {@link Flux} API and discover new operators is + * through the reference documentation, rather than through this javadoc (as opposed to + * learning more about individual operators). See the + * "which operator do I need?" appendix. + * + *

+ * + * + *

It is intended to be used in implementations and return types. Input parameters should keep using raw + * {@link Publisher} as much as possible. + * + *

If it is known that the underlying {@link Publisher} will emit 0 or 1 element, {@link Mono} should be used + * instead. + * + *

Note that using state in the {@code java.util.function} / lambdas used within Flux operators + * should be avoided, as these may be shared between several {@link Subscriber Subscribers}. + * + *

{@link #subscribe(CoreSubscriber)} is an internal extension to + * {@link #subscribe(Subscriber)} used internally for {@link Context} passing. User + * provided {@link Subscriber} may + * be passed to this "subscribe" extension but will loose the available + * per-subscribe {@link Hooks#onLastOperator}. + * + * @param the element type of this Reactive Streams {@link Publisher} + * + * @author Sebastien Deleuze + * @author Stephane Maldini + * @author David Karnok + * @author Simon Baslé + * + * @see Mono + */ +public abstract class Flux implements CorePublisher { + +// ============================================================================================================== +// Static Generators +// ============================================================================================================== + + /** + * Build a {@link Flux} whose data are generated by the combination of the + * most recently published value from each of the {@link Publisher} sources. + *

+ * + * + *

Discard Support: This operator is NOT suited for types that need guaranteed discard of unpropagated elements, as + * it doesn't track which elements have been used by the combinator and which haven't. Furthermore, elements can and + * will be passed to the combinator multiple times. + * + * @param sources The {@link Publisher} sources to combine values from + * @param combinator The aggregate function that will receive the latest value from each upstream and return the value + * to signal downstream + * @param type of the value from sources + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced combinations + */ + @SafeVarargs + public static Flux combineLatest(Function combinator, Publisher... sources) { + return combineLatest(combinator, Queues.XS_BUFFER_SIZE, sources); + } + + /** + * Build a {@link Flux} whose data are generated by the combination of the + * most recently published value from each of the {@link Publisher} sources. + *

+ * + * + *

Discard Support: This operator is NOT suited for types that need guaranteed discard of unpropagated elements, as + * it doesn't track which elements have been used by the combinator and which haven't. Furthermore, elements can and + * will be passed to the combinator multiple times. + * + * @param sources The {@link Publisher} sources to combine values from + * @param prefetch The demand sent to each combined source {@link Publisher} + * @param combinator The aggregate function that will receive the latest value from each upstream and return the value + * to signal downstream + * @param type of the value from sources + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced combinations + */ + @SafeVarargs + public static Flux combineLatest(Function combinator, int prefetch, + Publisher... sources) { + if (sources.length == 0) { + return empty(); + } + + if (sources.length == 1) { + Publisher source = sources[0]; + if (source instanceof Fuseable) { + return onAssembly(new FluxMapFuseable<>(from(source), + v -> combinator.apply(new Object[]{v}))); + } + return onAssembly(new FluxMap<>(from(source), + v -> combinator.apply(new Object[]{v}))); + } + + return onAssembly(new FluxCombineLatest<>(sources, + combinator, Queues.get(prefetch), prefetch)); + } + + /** + * Build a {@link Flux} whose data are generated by the combination of the + * most recently published value from each of two {@link Publisher} sources. + *

+ * + * + *

Discard Support: This operator is NOT suited for types that need guaranteed discard of unpropagated elements, as + * it doesn't track which elements have been used by the combinator and which haven't. Furthermore, elements can and + * will be passed to the combinator multiple times. + * + * @param source1 The first {@link Publisher} source to combine values from + * @param source2 The second {@link Publisher} source to combine values from + * @param combinator The aggregate function that will receive the latest value from each upstream and return the value + * to signal downstream + * @param type of the value from source1 + * @param type of the value from source2 + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced combinations + */ + @SuppressWarnings("unchecked") + public static Flux combineLatest(Publisher source1, + Publisher source2, + BiFunction combinator) { + return combineLatest(tuple -> combinator.apply((T1)tuple[0], (T2)tuple[1]), source1, source2); + } + + /** + * Build a {@link Flux} whose data are generated by the combination of the + * most recently published value from each of three {@link Publisher} sources. + *

+ * + * + *

Discard Support: This operator is NOT suited for types that need guaranteed discard of unpropagated elements, as + * it doesn't track which elements have been used by the combinator and which haven't. Furthermore, elements can and + * will be passed to the combinator multiple times. + * + * @param source1 The first {@link Publisher} source to combine values from + * @param source2 The second {@link Publisher} source to combine values from + * @param source3 The third {@link Publisher} source to combine values from + * @param combinator The aggregate function that will receive the latest value from each upstream and return the value + * to signal downstream + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced combinations + */ + public static Flux combineLatest(Publisher source1, + Publisher source2, + Publisher source3, + Function combinator) { + return combineLatest(combinator, source1, source2, source3); + } + + /** + * Build a {@link Flux} whose data are generated by the combination of the + * most recently published value from each of four {@link Publisher} sources. + *

+ * + * + *

Discard Support: This operator is NOT suited for types that need guaranteed discard of unpropagated elements, as + * it doesn't track which elements have been used by the combinator and which haven't. Furthermore, elements can and + * will be passed to the combinator multiple times. + * + * @param source1 The first {@link Publisher} source to combine values from + * @param source2 The second {@link Publisher} source to combine values from + * @param source3 The third {@link Publisher} source to combine values from + * @param source4 The fourth {@link Publisher} source to combine values from + * @param combinator The aggregate function that will receive the latest value from each upstream and return the value + * to signal downstream + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * @param type of the value from source4 + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced combinations + */ + public static Flux combineLatest(Publisher source1, + Publisher source2, + Publisher source3, + Publisher source4, + Function combinator) { + return combineLatest(combinator, source1, source2, source3, source4); + } + + /** + * Build a {@link Flux} whose data are generated by the combination of the + * most recently published value from each of five {@link Publisher} sources. + *

+ * + * + *

Discard Support: This operator is NOT suited for types that need guaranteed discard of unpropagated elements, as + * it doesn't track which elements have been used by the combinator and which haven't. Furthermore, elements can and + * will be passed to the combinator multiple times. + * + * @param source1 The first {@link Publisher} source to combine values from + * @param source2 The second {@link Publisher} source to combine values from + * @param source3 The third {@link Publisher} source to combine values from + * @param source4 The fourth {@link Publisher} source to combine values from + * @param source5 The fifth {@link Publisher} source to combine values from + * @param combinator The aggregate function that will receive the latest value from each upstream and return the value + * to signal downstream + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * @param type of the value from source4 + * @param type of the value from source5 + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced combinations + */ + public static Flux combineLatest(Publisher source1, + Publisher source2, + Publisher source3, + Publisher source4, + Publisher source5, + Function combinator) { + return combineLatest(combinator, source1, source2, source3, source4, source5); + } + + /** + * Build a {@link Flux} whose data are generated by the combination of the + * most recently published value from each of six {@link Publisher} sources. + *

+ * + * + *

Discard Support: This operator is NOT suited for types that need guaranteed discard of unpropagated elements, as + * it doesn't track which elements have been used by the combinator and which haven't. Furthermore, elements can and + * will be passed to the combinator multiple times. + * + * @param source1 The first {@link Publisher} source to combine values from + * @param source2 The second {@link Publisher} source to combine values from + * @param source3 The third {@link Publisher} source to combine values from + * @param source4 The fourth {@link Publisher} source to combine values from + * @param source5 The fifth {@link Publisher} source to combine values from + * @param source6 The sixth {@link Publisher} source to combine values from + * @param combinator The aggregate function that will receive the latest value from each upstream and return the value + * to signal downstream + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * @param type of the value from source4 + * @param type of the value from source5 + * @param type of the value from source6 + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced combinations + */ + public static Flux combineLatest(Publisher source1, + Publisher source2, + Publisher source3, + Publisher source4, + Publisher source5, + Publisher source6, + Function combinator) { + return combineLatest(combinator, source1, source2, source3, source4, source5, source6); + } + + /** + * Build a {@link Flux} whose data are generated by the combination of the + * most recently published value from each + * of the {@link Publisher} sources provided in an {@link Iterable}. + *

+ * + * + *

Discard Support: This operator is NOT suited for types that need guaranteed discard of unpropagated elements, as + * it doesn't track which elements have been used by the combinator and which haven't. Furthermore, elements can and + * will be passed to the combinator multiple times. + * + * @param sources The list of {@link Publisher} sources to combine values from + * @param combinator The aggregate function that will receive the latest value from each upstream and return the value + * to signal downstream + * @param The common base type of the values from sources + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced combinations + */ + public static Flux combineLatest(Iterable> sources, + Function combinator) { + return combineLatest(sources, Queues.XS_BUFFER_SIZE, combinator); + } + + /** + * Build a {@link Flux} whose data are generated by the combination of the + * most recently published value from each + * of the {@link Publisher} sources provided in an {@link Iterable}. + *

+ * + * + *

Discard Support: This operator is NOT suited for types that need guaranteed discard of unpropagated elements, as + * it doesn't track which elements have been used by the combinator and which haven't. Furthermore, elements can and + * will be passed to the combinator multiple times. + * + * @param sources The list of {@link Publisher} sources to combine values from + * @param prefetch demand produced to each combined source {@link Publisher} + * @param combinator The aggregate function that will receive the latest value from each upstream and return the value + * to signal downstream + * @param The common base type of the values from sources + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced combinations + */ + public static Flux combineLatest(Iterable> sources, + int prefetch, + Function combinator) { + + return onAssembly(new FluxCombineLatest(sources, + combinator, + Queues.get(prefetch), prefetch)); + } + + /** + * Concatenate all sources provided in an {@link Iterable}, forwarding elements + * emitted by the sources downstream. + *

+ * Concatenation is achieved by sequentially subscribing to the first source then + * waiting for it to complete before subscribing to the next, and so on until the + * last source completes. Any error interrupts the sequence immediately and is + * forwarded downstream. + *

+ * + * + * @param sources The {@link Iterable} of {@link Publisher} to concatenate + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} concatenating all source sequences + */ + public static Flux concat(Iterable> sources) { + return onAssembly(new FluxConcatIterable<>(sources)); + } + + /** + * Concatenates the values to the end of the {@link Flux} + *

+ * + * + * @param values The values to concatenate + * + * @return a new {@link Flux} concatenating all source sequences + */ + @SafeVarargs + public final Flux concatWithValues(T... values) { + return concatWith(Flux.fromArray(values)); + } + + /** + * Concatenate all sources emitted as an onNext signal from a parent {@link Publisher}, + * forwarding elements emitted by the sources downstream. + *

+ * Concatenation is achieved by sequentially subscribing to the first source then + * waiting for it to complete before subscribing to the next, and so on until the + * last source completes. Any error interrupts the sequence immediately and is + * forwarded downstream. + *

+ * + * + *

Discard Support: This operator discards elements it internally queued for backpressure upon cancellation. + * + * @param sources The {@link Publisher} of {@link Publisher} to concatenate + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} concatenating all inner sources sequences + */ + public static Flux concat(Publisher> sources) { + return concat(sources, Queues.XS_BUFFER_SIZE); + } + + /** + * Concatenate all sources emitted as an onNext signal from a parent {@link Publisher}, + * forwarding elements emitted by the sources downstream. + *

+ * Concatenation is achieved by sequentially subscribing to the first source then + * waiting for it to complete before subscribing to the next, and so on until the + * last source completes. Any error interrupts the sequence immediately and is + * forwarded downstream. + *

+ * + * + *

Discard Support: This operator discards elements it internally queued for backpressure upon cancellation. + * + * @param sources The {@link Publisher} of {@link Publisher} to concatenate + * @param prefetch the number of Publishers to prefetch from the outer {@link Publisher} + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} concatenating all inner sources sequences + */ + public static Flux concat(Publisher> sources, int prefetch) { + return from(sources).concatMap(identityFunction(), prefetch); + } + + /** + * Concatenate all sources provided as a vararg, forwarding elements emitted by the + * sources downstream. + *

+ * Concatenation is achieved by sequentially subscribing to the first source then + * waiting for it to complete before subscribing to the next, and so on until the + * last source completes. Any error interrupts the sequence immediately and is + * forwarded downstream. + *

+ * + * + * @param sources The {@link Publisher} of {@link Publisher} to concat + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} concatenating all source sequences + */ + @SafeVarargs + public static Flux concat(Publisher... sources) { + return onAssembly(new FluxConcatArray<>(false, sources)); + } + + /** + * Concatenate all sources emitted as an onNext signal from a parent {@link Publisher}, + * forwarding elements emitted by the sources downstream. + *

+ * Concatenation is achieved by sequentially subscribing to the first source then + * waiting for it to complete before subscribing to the next, and so on until the + * last source completes. Errors do not interrupt the main sequence but are propagated + * after the rest of the sources have had a chance to be concatenated. + *

+ * + * + * + *

Discard Support: This operator discards elements it internally queued for backpressure upon cancellation. + * + * @param sources The {@link Publisher} of {@link Publisher} to concatenate + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} concatenating all inner sources sequences, delaying errors + */ + public static Flux concatDelayError(Publisher> sources) { + return concatDelayError(sources, Queues.XS_BUFFER_SIZE); + } + + /** + * Concatenate all sources emitted as an onNext signal from a parent {@link Publisher}, + * forwarding elements emitted by the sources downstream. + *

+ * Concatenation is achieved by sequentially subscribing to the first source then + * waiting for it to complete before subscribing to the next, and so on until the + * last source completes. Errors do not interrupt the main sequence but are propagated + * after the rest of the sources have had a chance to be concatenated. + *

+ * + *

+ *

Discard Support: This operator discards elements it internally queued for backpressure upon cancellation. + * + * @param sources The {@link Publisher} of {@link Publisher} to concatenate + * @param prefetch number of elements to prefetch from the source, to be turned into inner Publishers + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} concatenating all inner sources sequences until complete or error + */ + public static Flux concatDelayError(Publisher> sources, int prefetch) { + return from(sources).concatMapDelayError(identityFunction(), prefetch); + } + + /** + * Concatenate all sources emitted as an onNext signal from a parent {@link Publisher}, + * forwarding elements emitted by the sources downstream. + *

+ * Concatenation is achieved by sequentially subscribing to the first source then + * waiting for it to complete before subscribing to the next, and so on until the + * last source completes. + *

+ * Errors do not interrupt the main sequence but are propagated after the current + * concat backlog if {@code delayUntilEnd} is {@literal false} or after all sources + * have had a chance to be concatenated if {@code delayUntilEnd} is {@literal true}. + *

+ * + *

+ * + *

Discard Support: This operator discards elements it internally queued for backpressure upon cancellation. + * + * @param sources The {@link Publisher} of {@link Publisher} to concatenate + * @param delayUntilEnd delay error until all sources have been consumed instead of + * after the current source + * @param prefetch the number of Publishers to prefetch from the outer {@link Publisher} + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} concatenating all inner sources sequences until complete or error + */ + public static Flux concatDelayError(Publisher> sources, boolean delayUntilEnd, int prefetch) { + return from(sources).concatMapDelayError(identityFunction(), delayUntilEnd, prefetch); + } + + /** + * Concatenate all sources provided as a vararg, forwarding elements emitted by the + * sources downstream. + *

+ * Concatenation is achieved by sequentially subscribing to the first source then + * waiting for it to complete before subscribing to the next, and so on until the + * last source completes. Errors do not interrupt the main sequence but are propagated + * after the rest of the sources have had a chance to be concatenated. + *

+ * + *

+ * + *

Discard Support: This operator discards elements it internally queued for backpressure upon cancellation. + * + * @param sources The {@link Publisher} of {@link Publisher} to concat + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} concatenating all source sequences + */ + @SafeVarargs + public static Flux concatDelayError(Publisher... sources) { + return onAssembly(new FluxConcatArray<>(true, sources)); + } + + /** + * Programmatically create a {@link Flux} with the capability of emitting multiple + * elements in a synchronous or asynchronous manner through the {@link FluxSink} API. + * This includes emitting elements from multiple threads. + *

+ * + *

+ * This Flux factory is useful if one wants to adapt some other multi-valued async API + * and not worry about cancellation and backpressure (which is handled by buffering + * all signals if the downstream can't keep up). + *

+ * For example: + * + *


+	 * Flux.<String>create(emitter -> {
+	 *
+	 *     ActionListener al = e -> {
+	 *         emitter.next(textField.getText());
+	 *     };
+	 *     // without cleanup support:
+	 *
+	 *     button.addActionListener(al);
+	 *
+	 *     // with cleanup support:
+	 *
+	 *     button.addActionListener(al);
+	 *     emitter.onDispose(() -> {
+	 *         button.removeListener(al);
+	 *     });
+	 * });
+	 * 
+ * + *

Discard Support: The {@link FluxSink} exposed by this operator buffers in case of + * overflow. The buffer is discarded when the main sequence is cancelled. + * + * @param The type of values in the sequence + * @param emitter Consume the {@link FluxSink} provided per-subscriber by Reactor to generate signals. + * @return a {@link Flux} + * @see #push(Consumer) + */ + public static Flux create(Consumer> emitter) { + return create(emitter, OverflowStrategy.BUFFER); + } + + /** + * Programmatically create a {@link Flux} with the capability of emitting multiple + * elements in a synchronous or asynchronous manner through the {@link FluxSink} API. + * This includes emitting elements from multiple threads. + *

+ * + *

+ * This Flux factory is useful if one wants to adapt some other multi-valued async API + * and not worry about cancellation and backpressure (which is handled by buffering + * all signals if the downstream can't keep up). + *

+ * For example: + * + *


+	 * Flux.<String>create(emitter -> {
+	 *
+	 *     ActionListener al = e -> {
+	 *         emitter.next(textField.getText());
+	 *     };
+	 *     // without cleanup support:
+	 *
+	 *     button.addActionListener(al);
+	 *
+	 *     // with cleanup support:
+	 *
+	 *     button.addActionListener(al);
+	 *     emitter.onDispose(() -> {
+	 *         button.removeListener(al);
+	 *     });
+	 * }, FluxSink.OverflowStrategy.LATEST);
+	 * 
+ * + *

Discard Support: The {@link FluxSink} exposed by this operator discards elements + * as relevant to the chosen {@link OverflowStrategy}. For example, the {@link OverflowStrategy#DROP} + * discards each items as they are being dropped, while {@link OverflowStrategy#BUFFER} + * will discard the buffer upon cancellation. + * + * @param The type of values in the sequence + * @param backpressure the backpressure mode, see {@link OverflowStrategy} for the + * 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) + */ + public static Flux create(Consumer> emitter, OverflowStrategy backpressure) { + return onAssembly(new FluxCreate<>(emitter, backpressure, FluxCreate.CreateMode.PUSH_PULL)); + } + + /** + * 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)}. + *

+ * + *

+ * This Flux factory is useful if one wants to adapt some other single-threaded + * multi-valued async API and not worry about cancellation and backpressure (which is + * handled by buffering all signals if the downstream can't keep up). + *

+ * For example: + * + *


+	 * Flux.<String>push(emitter -> {
+	 *
+	 *	 ActionListener al = e -> {
+	 *		 emitter.next(textField.getText());
+	 *	 };
+	 *	 // without cleanup support:
+	 *
+	 *	 button.addActionListener(al);
+	 *
+	 *	 // with cleanup support:
+	 *
+	 *	 button.addActionListener(al);
+	 *	 emitter.onDispose(() -> {
+	 *		 button.removeListener(al);
+	 *	 });
+	 * });
+	 * 
+ * + *

Discard Support: The {@link FluxSink} exposed by this operator buffers in case of + * overflow. The buffer is discarded when the main sequence is cancelled. + * + * @param The type of values in the sequence + * @param emitter Consume the {@link FluxSink} provided per-subscriber by Reactor to generate signals. + * @return a {@link Flux} + * @see #create(Consumer) + */ + public static Flux push(Consumer> emitter) { + return push(emitter, OverflowStrategy.BUFFER); + } + + /** + * 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)}. + *

+ * + *

+ * This Flux factory is useful if one wants to adapt some other single-threaded + * multi-valued async API and not worry about cancellation and backpressure (which is + * handled by buffering all signals if the downstream can't keep up). + *

+ * For example: + * + *


+	 * Flux.<String>push(emitter -> {
+	 *
+	 *	 ActionListener al = e -> {
+	 *		 emitter.next(textField.getText());
+	 *	 };
+	 *	 // without cleanup support:
+	 *
+	 *	 button.addActionListener(al);
+	 *
+	 *	 // with cleanup support:
+	 *
+	 *	 button.addActionListener(al);
+	 *	 emitter.onDispose(() -> {
+	 *		 button.removeListener(al);
+	 *	 });
+	 * }, FluxSink.OverflowStrategy.LATEST);
+	 * 
+ * + *

Discard Support: The {@link FluxSink} exposed by this operator discards elements + * as relevant to the chosen {@link OverflowStrategy}. For example, the {@link OverflowStrategy#DROP} + * discards each items as they are being dropped, while {@link OverflowStrategy#BUFFER} + * will discard the buffer upon cancellation. + * + * @param The type of values in the sequence + * @param backpressure the backpressure mode, see {@link OverflowStrategy} for the + * 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) + */ + public static Flux push(Consumer> emitter, OverflowStrategy backpressure) { + return onAssembly(new FluxCreate<>(emitter, backpressure, FluxCreate.CreateMode.PUSH_ONLY)); + } + + /** + * 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 Supplier} can create a subscriber-specific instance. + * If the supplier doesn't generate a new instance however, this operator will + * effectively behave like {@link #from(Publisher)}. + * + *

+ * + * + * @param supplier the {@link Publisher} {@link Supplier} to call on subscribe + * @param the type of values passing through the {@link Flux} + * + * @return a deferred {@link Flux} + * @see #deferContextual(Function) + */ + public static Flux defer(Supplier> supplier) { + return onAssembly(new FluxDefer<>(supplier)); + } + + /** + * 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 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)}. + * + *

+ * + * + * @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 + */ + public static Flux deferContextual(Function> contextualPublisherFactory) { + return onAssembly(new FluxDeferContextual<>(contextualPublisherFactory)); + } + + /** + * Create a {@link Flux} that completes without emitting any item. + *

+ * + * + * @param the reified type of the target {@link Subscriber} + * + * @return an empty {@link Flux} + */ + public static Flux empty() { + return FluxEmpty.instance(); + } + + /** + * Create a {@link Flux} that terminates with the specified error immediately after + * being subscribed to. + *

+ * + * + * @param error the error to signal to each {@link Subscriber} + * @param the reified type of the target {@link Subscriber} + * + * @return a new failing {@link Flux} + */ + public static Flux error(Throwable error) { + return error(error, false); + } + + /** + * Create a {@link Flux} that terminates with an error immediately after being + * subscribed to. The {@link Throwable} is generated by a {@link Supplier}, invoked + * each time there is a subscription and allowing for lazy instantiation. + *

+ * + * + * @param errorSupplier the error signal {@link Supplier} to invoke for each {@link Subscriber} + * @param the reified type of the target {@link Subscriber} + * + * @return a new failing {@link Flux} + */ + public static Flux error(Supplier errorSupplier) { + return onAssembly(new FluxErrorSupplied<>(errorSupplier)); + } + + /** + * Create a {@link Flux} that terminates with the specified error, either immediately + * after being subscribed to or after being first requested. + * + *

+ * + * + * @param throwable the error to signal to each {@link Subscriber} + * @param whenRequested if true, will onError on the first request instead of subscribe(). + * @param the reified type of the target {@link Subscriber} + * + * @return a new failing {@link Flux} + */ + public static Flux error(Throwable throwable, boolean whenRequested) { + if (whenRequested) { + return onAssembly(new FluxErrorOnRequest<>(throwable)); + } + else { + return onAssembly(new FluxError<>(throwable)); + } + } + + /** + * Pick the first {@link Publisher} to emit any signal (onNext/onError/onComplete) and + * replay all signals from that {@link Publisher}, effectively behaving like the + * fastest of these competing sources. + * + *

+ * + * + * @param sources The competing source publishers + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} behaving like the fastest of its sources + * @deprecated use {@link #firstWithSignal(Publisher[])}. To be removed in reactor 3.5. + */ + @SafeVarargs + @Deprecated + public static Flux first(Publisher... sources) { + return firstWithSignal(sources); + } + + /** + * Pick the first {@link Publisher} to emit any signal (onNext/onError/onComplete) and + * replay all signals from that {@link Publisher}, effectively behaving like the + * fastest of these competing sources. + * + *

+ * + * + * @param sources The competing source publishers + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} behaving like the fastest of its sources + * @deprecated use {@link #firstWithSignal(Iterable)}. To be removed in reactor 3.5. + */ + @Deprecated + public static Flux first(Iterable> sources) { + return firstWithSignal(sources); + } + + /** + * Pick the first {@link Publisher} to emit any signal (onNext/onError/onComplete) and + * replay all signals from that {@link Publisher}, effectively behaving like the + * fastest of these competing sources. + * + *

+ * + * + * @param sources The competing source publishers + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} behaving like the fastest of its sources + */ + @SafeVarargs + public static Flux firstWithSignal(Publisher... sources) { + return onAssembly(new FluxFirstWithSignal<>(sources)); + } + + /** + * Pick the first {@link Publisher} to emit any signal (onNext/onError/onComplete) and + * replay all signals from that {@link Publisher}, effectively behaving like the + * fastest of these competing sources. + * + *

+ * + * + * @param sources The competing source publishers + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} behaving like the fastest of its sources + */ + public static Flux firstWithSignal(Iterable> sources) { + return onAssembly(new FluxFirstWithSignal<>(sources)); + } + + /** + * Pick the first {@link Publisher} to emit any value and replay all values + * from that {@link Publisher}, effectively behaving like the source that first + * emits an {@link Subscriber#onNext(Object) onNext}. + * + *

+ * Sources with values always "win" over empty sources (ones that only emit onComplete) + * or failing sources (ones that only emit onError). + *

+ * When no source can provide a value, this operator fails with a {@link NoSuchElementException} + * (provided there are at least two sources). This exception has a {@link Exceptions#multiple(Throwable...) composite} + * as its {@link Throwable#getCause() cause} that can be used to inspect what went wrong with each source + * (so the composite has as many elements as there are sources). + *

+ * Exceptions from failing sources are directly reflected in the composite at the index of the failing source. + * For empty sources, a {@link NoSuchElementException} is added at their respective index. + * One can use {@link Exceptions#unwrapMultiple(Throwable) Exceptions.unwrapMultiple(topLevel.getCause())} + * to easily inspect these errors as a {@link List}. + *

+ * Note that like in {@link #firstWithSignal(Iterable)}, an infinite source can be problematic + * if no other source emits onNext. + *

+ * + * + * @param sources An {@link Iterable} of the competing source publishers + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} behaving like the fastest of its sources + */ + public static Flux firstWithValue(Iterable> sources) { + return onAssembly(new FluxFirstWithValue<>(sources)); + } + + /** + * Pick the first {@link Publisher} to emit any value and replay all values + * from that {@link Publisher}, effectively behaving like the source that first + * emits an {@link Subscriber#onNext(Object) onNext}. + *

+ * Sources with values always "win" over an empty source (ones that only emit onComplete) + * or failing sources (ones that only emit onError). + *

+ * When no source can provide a value, this operator fails with a {@link NoSuchElementException} + * (provided there are at least two sources). This exception has a {@link Exceptions#multiple(Throwable...) composite} + * as its {@link Throwable#getCause() cause} that can be used to inspect what went wrong with each source + * (so the composite has as many elements as there are sources). + *

+ * Exceptions from failing sources are directly reflected in the composite at the index of the failing source. + * For empty sources, a {@link NoSuchElementException} is added at their respective index. + * One can use {@link Exceptions#unwrapMultiple(Throwable) Exceptions.unwrapMultiple(topLevel.getCause())} + * to easily inspect these errors as a {@link List}. + *

+ * Note that like in {@link #firstWithSignal(Publisher[])}, an infinite source can be problematic + * if no other source emits onNext. + * In case the {@code first} source is already an array-based {@link #firstWithValue(Publisher, Publisher[])} + * instance, nesting is avoided: a single new array-based instance is created with all the + * sources from {@code first} plus all the {@code others} sources at the same level. + *

+ * + * + * @param first The first competing source publisher + * @param others The other competing source publishers + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} behaving like the fastest of its sources + */ + @SafeVarargs + public static Flux firstWithValue(Publisher first, Publisher... others) { + if (first instanceof FluxFirstWithValue) { + @SuppressWarnings("unchecked") + FluxFirstWithValue orPublisher = (FluxFirstWithValue) first; + + FluxFirstWithValue result = orPublisher.firstValuedAdditionalSources(others); + if (result != null) { + return result; + } + } + return onAssembly(new FluxFirstWithValue<>(first, others)); + } + + /** + * Decorate the specified {@link Publisher} with the {@link Flux} API. + *

+ * + *

+ * {@link Hooks#onEachOperator(String, Function)} and similar assembly hooks are applied + * unless the source is already a {@link Flux}. + * + * @param source the source to decorate + * @param The type of values in both source and output sequences + * + * @return a new {@link Flux} + */ + public static Flux from(Publisher source) { + //duplicated in wrap, but necessary to detect early and thus avoid applying assembly + if (source instanceof Flux) { + @SuppressWarnings("unchecked") + Flux casted = (Flux) source; + return casted; + } + + //all other cases (including ScalarCallable) are managed without assembly in wrap + //let onAssembly point to Flux.from: + return onAssembly(wrap(source)); + } + + /** + * Create a {@link Flux} that emits the items contained in the provided array. + *

+ * + * + * @param array the array to read data from + * @param The type of values in the source array and resulting Flux + * + * @return a new {@link Flux} + */ + public static Flux fromArray(T[] array) { + if (array.length == 0) { + return empty(); + } + if (array.length == 1) { + return just(array[0]); + } + return onAssembly(new FluxArray<>(array)); + } + + /** + * Create a {@link Flux} that emits the items contained in the provided {@link Iterable}. + * The {@link Iterable#iterator()} method will be invoked at least once and at most twice + * for each subscriber. + *

+ * + *

+ * This operator inspects the {@link Iterable}'s {@link Spliterator} to assess if the iteration + * can be guaranteed to be finite (see {@link Operators#onDiscardMultiple(Iterator, boolean, Context)}). + * Since the default Spliterator wraps the Iterator we can have two {@link Iterable#iterator()} + * calls. This second invocation is skipped on a {@link Collection} however, a type which is + * assumed to be always finite. + * + *

Discard Support: Upon cancellation, this operator attempts to discard the remainder of the + * {@link Iterable} if it can safely ensure the iterator is finite. + * Note that this means the {@link Iterable#iterator()} method could be invoked twice. + * + * @param it the {@link Iterable} to read data from + * @param The type of values in the source {@link Iterable} and resulting Flux + * + * @return a new {@link Flux} + */ + public static Flux fromIterable(Iterable it) { + return onAssembly(new FluxIterable<>(it)); + } + + /** + * Create a {@link Flux} that emits the items contained in the provided {@link Stream}. + * Keep in mind that a {@link Stream} cannot be re-used, which can be problematic in + * case of multiple subscriptions or re-subscription (like with {@link #repeat()} or + * {@link #retry()}). The {@link Stream} is {@link Stream#close() closed} automatically + * by the operator on cancellation, error or completion. + *

+ * + * + *

Discard Support: Upon cancellation, this operator attempts to discard remainder of the + * {@link Stream} through its open {@link Spliterator}, if it can safely ensure it is finite + * (see {@link Operators#onDiscardMultiple(Iterator, boolean, Context)}). + * + * @param s the {@link Stream} to read data from + * @param The type of values in the source {@link Stream} and resulting Flux + * + * @return a new {@link Flux} + */ + public static Flux fromStream(Stream s) { + Objects.requireNonNull(s, "Stream s must be provided"); + return onAssembly(new FluxStream<>(() -> s)); + } + + /** + * Create a {@link Flux} that emits the items contained in a {@link Stream} created by + * the provided {@link Supplier} for each subscription. The {@link Stream} is + * {@link Stream#close() closed} automatically by the operator on cancellation, error + * or completion. + *

+ * + * + *

Discard Support: Upon cancellation, this operator attempts to discard remainder of the + * {@link Stream} through its open {@link Spliterator}, if it can safely ensure it is finite + * (see {@link Operators#onDiscardMultiple(Iterator, boolean, Context)}). + * + * @param streamSupplier the {@link Supplier} that generates the {@link Stream} from + * which to read data + * @param The type of values in the source {@link Stream} and resulting Flux + * + * @return a new {@link Flux} + */ + public static Flux fromStream(Supplier> streamSupplier) { + return onAssembly(new FluxStream<>(streamSupplier)); + } + + /** + * Programmatically create a {@link Flux} by generating signals one-by-one via a + * consumer callback. + *

+ * + * + * @param the value type emitted + * @param generator Consume the {@link SynchronousSink} provided per-subscriber by Reactor + * to generate a single signal on each pass. + * + * @return a {@link Flux} + */ + public static Flux generate(Consumer> generator) { + Objects.requireNonNull(generator, "generator"); + return onAssembly(new FluxGenerate<>(generator)); + } + + /** + * Programmatically create a {@link Flux} by generating signals one-by-one via a + * consumer callback and some state. The {@code stateSupplier} may return {@literal null}. + *

+ * + * + * @param the value type emitted + * @param the per-subscriber custom state type + * @param stateSupplier called for each incoming Subscriber to provide the initial state for the generator bifunction + * @param generator Consume the {@link SynchronousSink} provided per-subscriber by Reactor + * as well as the current state to generate a single signal on each pass + * and return a (new) state. + * @return a {@link Flux} + */ + public static Flux generate(Callable stateSupplier, BiFunction, S> generator) { + return onAssembly(new FluxGenerate<>(stateSupplier, generator)); + } + + /** + * Programmatically create a {@link Flux} by generating signals one-by-one via a + * consumer callback and some state, with a final cleanup callback. The + * {@code stateSupplier} may return {@literal null} but your cleanup {@code stateConsumer} + * will need to handle the null case. + *

+ * + * + * @param the value type emitted + * @param the per-subscriber custom state type + * @param stateSupplier called for each incoming Subscriber to provide the initial state for the generator bifunction + * @param generator Consume the {@link SynchronousSink} provided per-subscriber by Reactor + * as well as the current state to generate a single signal on each pass + * and return a (new) state. + * @param stateConsumer called after the generator has terminated or the downstream cancelled, receiving the last + * state to be handled (i.e., release resources or do other cleanup). + * + * @return a {@link Flux} + */ + public static Flux generate(Callable stateSupplier, BiFunction, S> generator, Consumer stateConsumer) { + return onAssembly(new FluxGenerate<>(stateSupplier, generator, stateConsumer)); + } + + /** + * Create a {@link Flux} that emits long values starting with 0 and incrementing at + * specified time intervals on the global timer. The first element is emitted after + * an initial delay equal to the {@code period}. If demand is not produced in time, + * an onError will be signalled with an {@link Exceptions#isOverflow(Throwable) overflow} + * {@code IllegalStateException} detailing the tick that couldn't be emitted. + * In normal conditions, the {@link Flux} will never complete. + *

+ * Runs on the {@link Schedulers#parallel()} Scheduler. + *

+ * + * + * @param period the period {@link Duration} between each increment + * @return a new {@link Flux} emitting increasing numbers at regular intervals + */ + public static Flux interval(Duration period) { + return interval(period, Schedulers.parallel()); + } + + /** + * Create a {@link Flux} that emits long values starting with 0 and incrementing at + * specified time intervals, after an initial delay, on the global timer. If demand is + * not produced in time, an onError will be signalled with an + * {@link Exceptions#isOverflow(Throwable) overflow} {@code IllegalStateException} + * detailing the tick that couldn't be emitted. In normal conditions, the {@link Flux} + * will never complete. + *

+ * Runs on the {@link Schedulers#parallel()} Scheduler. + *

+ * + * + * @param delay the {@link Duration} to wait before emitting 0l + * @param period the period {@link Duration} before each following increment + * + * @return a new {@link Flux} emitting increasing numbers at regular intervals + */ + public static Flux interval(Duration delay, Duration period) { + return interval(delay, period, Schedulers.parallel()); + } + + /** + * Create a {@link Flux} that emits long values starting with 0 and incrementing at + * specified time intervals, on the specified {@link Scheduler}. The first element is + * emitted after an initial delay equal to the {@code period}. If demand is not + * produced in time, an onError will be signalled with an {@link Exceptions#isOverflow(Throwable) overflow} + * {@code IllegalStateException} detailing the tick that couldn't be emitted. + * In normal conditions, the {@link Flux} will never complete. + *

+ * + * + * @param period the period {@link Duration} between each increment + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a new {@link Flux} emitting increasing numbers at regular intervals + */ + public static Flux interval(Duration period, Scheduler timer) { + return interval(period, period, timer); + } + + /** + * Create a {@link Flux} that emits long values starting with 0 and incrementing at + * specified time intervals, after an initial delay, on the specified {@link Scheduler}. + * If demand is not produced in time, an onError will be signalled with an + * {@link Exceptions#isOverflow(Throwable) overflow} {@code IllegalStateException} + * detailing the tick that couldn't be emitted. In normal conditions, the {@link Flux} + * will never complete. + *

+ * + * + * @param delay the {@link Duration} to wait before emitting 0l + * @param period the period {@link Duration} before each following increment + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a new {@link Flux} emitting increasing numbers at regular intervals + */ + public static Flux interval(Duration delay, Duration period, Scheduler timer) { + return onAssembly(new FluxInterval(delay.toNanos(), period.toNanos(), TimeUnit.NANOSECONDS, timer)); + } + + /** + * Create a {@link Flux} that emits the provided elements and then completes. + *

+ * + * + * @param data the elements to emit, as a vararg + * @param the emitted data type + * + * @return a new {@link Flux} + */ + @SafeVarargs + public static Flux just(T... data) { + return fromArray(data); + } + + /** + * Create a new {@link Flux} that will only emit a single element then onComplete. + *

+ * + * + * @param data the single element to emit + * @param the emitted data type + * + * @return a new {@link Flux} + */ + public static Flux just(T data) { + return onAssembly(new FluxJust<>(data)); + } + + /** + * Merge data from {@link Publisher} sequences emitted by the passed {@link Publisher} + * into an interleaved merged sequence. Unlike {@link #concat(Publisher) concat}, inner + * sources are subscribed to eagerly. + *

+ * + * + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + * + * @param source a {@link Publisher} of {@link Publisher} sources to merge + * @param the merged type + * + * @return a merged {@link Flux} + */ + public static Flux merge(Publisher> source) { + return merge(source, + Queues.SMALL_BUFFER_SIZE, + Queues.XS_BUFFER_SIZE); + } + + /** + * Merge data from {@link Publisher} sequences emitted by the passed {@link Publisher} + * into an interleaved merged sequence. Unlike {@link #concat(Publisher) concat}, inner + * sources are subscribed to eagerly (but at most {@code concurrency} sources are + * subscribed to at the same time). + *

+ * + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + * + * @param source a {@link Publisher} of {@link Publisher} sources to merge + * @param concurrency the request produced to the main source thus limiting concurrent merge backlog + * @param the merged type + * + * @return a merged {@link Flux} + */ + public static Flux merge(Publisher> source, int concurrency) { + return merge(source, concurrency, Queues.XS_BUFFER_SIZE); + } + + /** + * Merge data from {@link Publisher} sequences emitted by the passed {@link Publisher} + * into an interleaved merged sequence. Unlike {@link #concat(Publisher) concat}, inner + * sources are subscribed to eagerly (but at most {@code concurrency} sources are + * subscribed to at the same time). + *

+ * + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + * + * @param source a {@link Publisher} of {@link Publisher} sources to merge + * @param concurrency the request produced to the main source thus limiting concurrent merge backlog + * @param prefetch the inner source request size + * @param the merged type + * + * @return a merged {@link Flux} + */ + public static Flux merge(Publisher> source, int concurrency, int prefetch) { + return onAssembly(new FluxFlatMap<>( + from(source), + identityFunction(), + false, + concurrency, + Queues.get(concurrency), + prefetch, + Queues.get(prefetch))); + } + + /** + * Merge data from {@link Publisher} sequences contained in an {@link Iterable} + * into an interleaved merged sequence. Unlike {@link #concat(Publisher) concat}, inner + * sources are subscribed to eagerly. + * A new {@link Iterator} will be created for each subscriber. + *

+ * + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + * + * @param sources the {@link Iterable} of sources to merge (will be lazily iterated on subscribe) + * @param The source type of the data sequence + * + * @return a merged {@link Flux} + */ + public static Flux merge(Iterable> sources) { + return merge(fromIterable(sources)); + } + + /** + * Merge data from {@link Publisher} sequences contained in an array / vararg + * into an interleaved merged sequence. Unlike {@link #concat(Publisher) concat}, + * sources are subscribed to eagerly. + *

+ * + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + * + * @param sources the array of {@link Publisher} sources to merge + * @param The source type of the data sequence + * + * @return a merged {@link Flux} + */ + @SafeVarargs + public static Flux merge(Publisher... sources) { + return merge(Queues.XS_BUFFER_SIZE, sources); + } + + /** + * Merge data from {@link Publisher} sequences contained in an array / vararg + * into an interleaved merged sequence. Unlike {@link #concat(Publisher) concat}, + * sources are subscribed to eagerly. + *

+ * + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + * + * @param sources the array of {@link Publisher} sources to merge + * @param prefetch the inner source request size + * @param The source type of the data sequence + * + * @return a fresh Reactive {@link Flux} publisher ready to be subscribed + */ + @SafeVarargs + public static Flux merge(int prefetch, Publisher... sources) { + return merge(prefetch, false, sources); + } + + /** + * Merge data from {@link Publisher} sequences contained in an array / vararg + * into an interleaved merged sequence. Unlike {@link #concat(Publisher) concat}, + * sources are subscribed to eagerly. + * This variant will delay any error until after the rest of the merge backlog has been processed. + *

+ * + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + * + * @param sources the array of {@link Publisher} sources to merge + * @param prefetch the inner source request size + * @param The source type of the data sequence + * + * @return a fresh Reactive {@link Flux} publisher ready to be subscribed + */ + @SafeVarargs + public static Flux mergeDelayError(int prefetch, Publisher... sources) { + return merge(prefetch, true, 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. + *

+ * Instead, this operator considers only one value from each source and picks the + * smallest of all these values, then replenishes the slot for that picked source. + *

+ * + * + * @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 , subscribing early but keeping the original ordering + */ + @SafeVarargs + public static > Flux mergeComparing(Publisher... sources) { + return mergeComparing(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}). This is not a {@link #sort(Comparator)}, as it doesn't consider + * the whole of each sequences. + *

+ * Instead, this operator considers only one value from each source and picks the + * smallest of all these values, then replenishes the slot for that picked source. + *

+ * + * + * @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 latest values from each source, using the + * smallest value and replenishing the source that produced it + */ + @SafeVarargs + public static Flux mergeComparing(Comparator comparator, Publisher... sources) { + return mergeComparing(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}). This is not a {@link #sort(Comparator)}, as it doesn't consider + * the whole of each sequences. + *

+ * Instead, this operator considers only one value from each source and picks the + * smallest of all these values, then replenishes the slot for that picked source. + *

+ * + * + * @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 latest values from each source, using the + * smallest value and replenishing the source that produced it + */ + @SafeVarargs + public static Flux mergeComparing(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, 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}). This is not a {@link #sort(Comparator)}, as it doesn't consider + * the whole of each sequences. + *

+ * Instead, this operator considers only one value from each source and picks the + * smallest of all these values, then replenishes the slot for that picked source. + *

+ * 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 latest values from each source, using the + * smallest value and replenishing the source that produced it + */ + @SafeVarargs + public static Flux mergeComparingDelayError(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, 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. + *

+ * Instead, this operator considers only one value from each source and picks the + * smallest of all these values, then replenishes the slot for that picked source. + *

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

+ * + * + * @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 latest values from each source, using the + * smallest value and replenishing the source that produced it + * @deprecated Use {@link #mergeComparingDelayError(int, Comparator, Publisher[])} instead + * (as {@link #mergeComparing(Publisher[])} don't have this operator's delayError behavior). + * To be removed in 3.6.0 at the earliest. + */ + @SafeVarargs + @Deprecated + public static > Flux mergeOrdered(Publisher... sources) { + return mergeOrdered(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}). This is not a {@link #sort(Comparator)}, as it doesn't consider + * the whole of each sequences. + *

+ * Instead, this operator considers only one value from each source and picks the + * smallest of all these values, then replenishes the slot for that picked source. + *

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

+ * + * + * @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 latest values from each source, using the + * smallest value and replenishing the source that produced it + * @deprecated Use {@link #mergeComparingDelayError(int, Comparator, Publisher[])} instead + * (as {@link #mergeComparing(Publisher[])} don't have this operator's delayError behavior). + * To be removed in 3.6.0 at the earliest. + */ + @SafeVarargs + @Deprecated + public static Flux mergeOrdered(Comparator comparator, Publisher... sources) { + return mergeOrdered(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}). This is not a {@link #sort(Comparator)}, as it doesn't consider + * the whole of each sequences. + *

+ * Instead, this operator considers only one value from each source and picks the + * smallest of all these values, then replenishes the slot for that picked source. + *

+ * 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 latest values from each source, using the + * smallest value and replenishing the source that produced it + * @deprecated Use {@link #mergeComparingDelayError(int, Comparator, Publisher[])} instead + * (as {@link #mergeComparing(Publisher[])} don't have this operator's delayError behavior). + * To be removed in 3.6.0 at the earliest. + */ + @SafeVarargs + @Deprecated + public static Flux mergeOrdered(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, sources)); + } + + /** + * Merge data from {@link Publisher} sequences emitted by the passed {@link Publisher} + * into an ordered merged sequence. Unlike concat, the inner publishers are subscribed to + * eagerly. Unlike merge, their emitted values are merged into the final sequence in + * subscription order. + *

+ * + * + * @param sources a {@link Publisher} of {@link Publisher} sources to merge + * @param the merged type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public static Flux mergeSequential(Publisher> sources) { + return mergeSequential(sources, false, Queues.SMALL_BUFFER_SIZE, + Queues.XS_BUFFER_SIZE); + } + + /** + * Merge data from {@link Publisher} sequences emitted by the passed {@link Publisher} + * into an ordered merged sequence. Unlike concat, the inner publishers are subscribed to + * eagerly (but at most {@code maxConcurrency} sources at a time). Unlike merge, their + * emitted values are merged into the final sequence in subscription order. + *

+ * + * + * @param sources a {@link Publisher} of {@link Publisher} sources to merge + * @param prefetch the inner source request size + * @param maxConcurrency the request produced to the main source thus limiting concurrent merge backlog + * @param the merged type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public static Flux mergeSequential(Publisher> sources, + int maxConcurrency, int prefetch) { + return mergeSequential(sources, false, maxConcurrency, prefetch); + } + + /** + * Merge data from {@link Publisher} sequences emitted by the passed {@link Publisher} + * into an ordered merged sequence. Unlike concat, the inner publishers are subscribed to + * eagerly (but at most {@code maxConcurrency} sources at a time). Unlike merge, their + * emitted values are merged into the final sequence in subscription order. + * This variant will delay any error until after the rest of the mergeSequential backlog has been processed. + *

+ * + * + * @param sources a {@link Publisher} of {@link Publisher} sources to merge + * @param prefetch the inner source request size + * @param maxConcurrency the request produced to the main source thus limiting concurrent merge backlog + * @param the merged type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public static Flux mergeSequentialDelayError(Publisher> sources, + int maxConcurrency, int prefetch) { + return mergeSequential(sources, true, maxConcurrency, prefetch); + } + + /** + * Merge data from {@link Publisher} sequences provided in an array/vararg + * into an ordered merged sequence. Unlike concat, sources are subscribed to + * eagerly. Unlike merge, their emitted values are merged into the final sequence in subscription order. + *

+ * + * + * @param sources a number of {@link Publisher} sequences to merge + * @param the merged type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + @SafeVarargs + public static Flux mergeSequential(Publisher... sources) { + return mergeSequential(Queues.XS_BUFFER_SIZE, false, sources); + } + + /** + * Merge data from {@link Publisher} sequences provided in an array/vararg + * into an ordered merged sequence. Unlike concat, sources are subscribed to + * eagerly. Unlike merge, their emitted values are merged into the final sequence in subscription order. + *

+ * + * + * @param prefetch the inner source request size + * @param sources a number of {@link Publisher} sequences to merge + * @param the merged type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + @SafeVarargs + public static Flux mergeSequential(int prefetch, Publisher... sources) { + return mergeSequential(prefetch, false, sources); + } + + /** + * Merge data from {@link Publisher} sequences provided in an array/vararg + * into an ordered merged sequence. Unlike concat, sources are subscribed to + * eagerly. Unlike merge, their emitted values are merged into the final sequence in subscription order. + * This variant will delay any error until after the rest of the mergeSequential backlog + * has been processed. + *

+ * + * + * @param prefetch the inner source request size + * @param sources a number of {@link Publisher} sequences to merge + * @param the merged type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + @SafeVarargs + public static Flux mergeSequentialDelayError(int prefetch, Publisher... sources) { + return mergeSequential(prefetch, true, sources); + } + + /** + * Merge data from {@link Publisher} sequences provided in an {@link Iterable} + * into an ordered merged sequence. Unlike concat, sources are subscribed to + * eagerly. Unlike merge, their emitted values are merged into the final sequence in subscription order. + *

+ * + * + * @param sources an {@link Iterable} of {@link Publisher} sequences to merge + * @param the merged type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public static Flux mergeSequential(Iterable> sources) { + return mergeSequential(sources, false, Queues.SMALL_BUFFER_SIZE, + Queues.XS_BUFFER_SIZE); + } + + /** + * Merge data from {@link Publisher} sequences provided in an {@link Iterable} + * into an ordered merged sequence. Unlike concat, sources are subscribed to + * eagerly (but at most {@code maxConcurrency} sources at a time). Unlike merge, their + * emitted values are merged into the final sequence in subscription order. + *

+ * + * + * @param sources an {@link Iterable} of {@link Publisher} sequences to merge + * @param maxConcurrency the request produced to the main source thus limiting concurrent merge backlog + * @param prefetch the inner source request size + * @param the merged type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public static Flux mergeSequential(Iterable> sources, + int maxConcurrency, int prefetch) { + return mergeSequential(sources, false, maxConcurrency, prefetch); + } + + /** + * Merge data from {@link Publisher} sequences provided in an {@link Iterable} + * into an ordered merged sequence. Unlike concat, sources are subscribed to + * eagerly (but at most {@code maxConcurrency} sources at a time). Unlike merge, their + * emitted values are merged into the final sequence in subscription order. + * This variant will delay any error until after the rest of the mergeSequential backlog + * has been processed. + *

+ * + * + * @param sources an {@link Iterable} of {@link Publisher} sequences to merge + * @param maxConcurrency the request produced to the main source thus limiting concurrent merge backlog + * @param prefetch the inner source request size + * @param the merged type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public static Flux mergeSequentialDelayError(Iterable> sources, + int maxConcurrency, int prefetch) { + return mergeSequential(sources, true, maxConcurrency, prefetch); + } + + /** + * Create a {@link Flux} that will never signal any data, error or completion signal. + *

+ * + * + * @param the {@link Subscriber} type target + * + * @return a never completing {@link Flux} + */ + public static Flux never() { + return FluxNever.instance(); + } + + /** + * Build a {@link Flux} that will only emit a sequence of {@code count} incrementing integers, + * starting from {@code start}. That is, emit integers between {@code start} (included) + * and {@code start + count} (excluded) then complete. + * + *

+ * + * + * @param start the first integer to be emit + * @param count the total number of incrementing values to emit, including the first value + * @return a ranged {@link Flux} + */ + public static Flux range(int start, int count) { + if (count == 1) { + return just(start); + } + if (count == 0) { + return empty(); + } + return onAssembly(new FluxRange(start, count)); + } + + /** + * 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. + *

+ * 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. + *

+ * + * + * @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 + */ + public static Flux switchOnNext(Publisher> mergedPublishers) { + return switchOnNext(mergedPublishers, Queues.XS_BUFFER_SIZE); + } + + /** + * 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. + *

+ * 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. + *

+ * + * + * @param mergedPublishers The {@link Publisher} of {@link Publisher} to switch on and mirror. + * @param prefetch the inner source request size + * @param the produced type + * + * @return a {@link FluxProcessor} 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 + * behavior of the single-parameter variant will then change to prefetch=0. + */ + @Deprecated + public static Flux switchOnNext(Publisher> mergedPublishers, int prefetch) { + if (prefetch == 0) { + return onAssembly(new FluxSwitchMapNoPrefetch<>(from(mergedPublishers), + identityFunction())); + } + return onAssembly(new FluxSwitchMap<>(from(mergedPublishers), + identityFunction(), + Queues.unbounded(prefetch), prefetch)); + } + + /** + * Uses a resource, generated by a supplier for each individual Subscriber, while streaming the values from a + * Publisher derived from the same resource and makes sure the resource is released if the sequence terminates or + * the Subscriber cancels. + *

+ * Eager resource cleanup happens just before the source termination and exceptions raised by the cleanup Consumer + * may override the terminal event. + *

+ * + *

+ * For an asynchronous version of the cleanup, with distinct path for onComplete, onError + * and cancel terminations, see {@link #usingWhen(Publisher, Function, Function, BiFunction, Function)}. + * + * @param resourceSupplier a {@link Callable} that is called on subscribe to generate the resource + * @param sourceSupplier a factory to derive a {@link Publisher} from the supplied resource + * @param resourceCleanup a resource cleanup callback invoked on completion + * @param emitted type + * @param resource type + * + * @return a new {@link Flux} built around a disposable resource + * @see #usingWhen(Publisher, Function, Function, BiFunction, Function) + * @see #usingWhen(Publisher, Function, Function) + */ + public static Flux using(Callable resourceSupplier, Function> sourceSupplier, Consumer resourceCleanup) { + return using(resourceSupplier, sourceSupplier, resourceCleanup, true); + } + + /** + * Uses a resource, generated by a supplier for each individual Subscriber, while streaming the values from a + * Publisher derived from the same resource and makes sure the resource is released if the sequence terminates or + * the Subscriber cancels. + *

+ *

  • Eager resource cleanup happens just before the source termination and exceptions raised by the cleanup + * Consumer may override the terminal event.
  • Non-eager cleanup will drop any exception.
+ *

+ * + *

+ * For an asynchronous version of the cleanup, with distinct path for onComplete, onError + * and cancel terminations, see {@link #usingWhen(Publisher, Function, Function, BiFunction, Function)}. + * + * @param resourceSupplier a {@link Callable} that is called on subscribe to generate the resource + * @param sourceSupplier a factory to derive a {@link Publisher} from the supplied resource + * @param resourceCleanup a resource cleanup callback invoked on completion + * @param eager true to clean before terminating downstream subscribers + * @param emitted type + * @param resource type + * + * @return a new {@link Flux} built around a disposable resource + * @see #usingWhen(Publisher, Function, Function, BiFunction, Function) + * @see #usingWhen(Publisher, Function, Function) + */ + public static Flux using(Callable resourceSupplier, Function> sourceSupplier, Consumer resourceCleanup, boolean eager) { + return onAssembly(new FluxUsing<>(resourceSupplier, + sourceSupplier, + resourceCleanup, + eager)); + } + + /** + * Uses a resource, generated by a {@link Publisher} for each individual {@link Subscriber}, + * while streaming the values from a {@link Publisher} derived from the same resource. + * Whenever the resulting sequence terminates, a provided {@link Function} generates + * a "cleanup" {@link Publisher} that is invoked but doesn't change the content of the + * main sequence. Instead it just defers the termination (unless it errors, in which case + * the error suppresses the original termination signal). + *

+ * Note that if the resource supplying {@link Publisher} emits more than one resource, the + * subsequent resources are dropped ({@link Operators#onNextDropped(Object, Context)}). If + * the publisher errors AFTER having emitted one resource, the error is also silently dropped + * ({@link Operators#onErrorDropped(Throwable, Context)}). + * An empty completion or error without at least one onNext signal triggers a short-circuit + * of the main sequence with the same terminal signal (no resource is established, no + * cleanup is invoked). + * + *

+ * + * + * @param resourceSupplier a {@link Publisher} that "generates" the resource, + * subscribed for each subscription to the main sequence + * @param resourceClosure a factory to derive a {@link Publisher} from the supplied resource + * @param asyncCleanup an asynchronous resource cleanup invoked when the resource + * closure terminates (with onComplete, onError or cancel) + * @param the type of elements emitted by the resource closure, and thus the main sequence + * @param the type of the resource object + * @return a new {@link Flux} built around a "transactional" resource, with asynchronous + * cleanup on all terminations (onComplete, onError, cancel) + */ + public static Flux usingWhen(Publisher resourceSupplier, + Function> resourceClosure, + Function> asyncCleanup) { + return usingWhen(resourceSupplier, resourceClosure, asyncCleanup, (resource, error) -> asyncCleanup.apply(resource), asyncCleanup); + } + + /** + * Uses a resource, generated by a {@link Publisher} for each individual {@link Subscriber}, + * while streaming the values from a {@link Publisher} derived from the same resource. + * Note that all steps of the operator chain that would need the resource to be in an open + * stable state need to be described inside the {@code resourceClosure} {@link Function}. + *

+ * Whenever the resulting sequence terminates, the relevant {@link Function} generates + * a "cleanup" {@link Publisher} that is invoked but doesn't change the content of the + * main sequence. Instead it just defers the termination (unless it errors, in which case + * the error suppresses the original termination signal). + * + *

+ * + *

+ * Individual cleanups can also be associated with main sequence cancellation and + * error terminations: + *

+ * + *

+ * Note that if the resource supplying {@link Publisher} emits more than one resource, the + * subsequent resources are dropped ({@link Operators#onNextDropped(Object, Context)}). If + * the publisher errors AFTER having emitted one resource, the error is also silently dropped + * ({@link Operators#onErrorDropped(Throwable, Context)}). + * An empty completion or error without at least one onNext signal triggers a short-circuit + * of the main sequence with the same terminal signal (no resource is established, no + * cleanup is invoked). + *

+ * Additionally, the terminal signal is replaced by any error that might have happened + * in the terminating {@link Publisher}: + *

+ * + *

+ * Finally, early cancellations will cancel the resource supplying {@link Publisher}: + *

+ * + * + * @param resourceSupplier a {@link Publisher} that "generates" the resource, + * subscribed for each subscription to the main sequence + * @param resourceClosure a factory to derive a {@link Publisher} from the supplied resource + * @param asyncComplete an asynchronous resource cleanup invoked if the resource closure terminates with onComplete + * @param asyncError an asynchronous resource cleanup invoked if the resource closure terminates with onError. + * The terminating error is provided to the {@link BiFunction} + * @param asyncCancel an asynchronous resource cleanup invoked if the resource closure is cancelled. + * When {@code null}, the {@code asyncComplete} path is used instead. + * @param the type of elements emitted by the resource closure, and thus the main sequence + * @param the type of the resource object + * @return a new {@link Flux} built around a "transactional" resource, with several + * termination path triggering asynchronous cleanup sequences + * @see #usingWhen(Publisher, Function, Function) + */ + public static Flux usingWhen(Publisher resourceSupplier, + Function> resourceClosure, + Function> asyncComplete, + BiFunction> asyncError, + //the operator itself accepts null for asyncCancel, but we won't in the public API + Function> asyncCancel) { + return onAssembly(new FluxUsingWhen<>(resourceSupplier, resourceClosure, + asyncComplete, asyncError, asyncCancel)); + } + + /** + * Zip two sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into an output value (constructed by the provided + * combinator). The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source1 The first {@link Publisher} source to zip. + * @param source2 The second {@link Publisher} source to zip. + * @param combinator The aggregate function that will receive a unique value from each upstream and return the + * value to signal downstream + * @param type of the value from source1 + * @param type of the value from source2 + * @param The produced output after transformation by the combinator + * + * @return a zipped {@link Flux} + */ + public static Flux zip(Publisher source1, + Publisher source2, + final BiFunction combinator) { + + return onAssembly(new FluxZip(source1, + source2, + combinator, + Queues.xs(), + Queues.XS_BUFFER_SIZE)); + } + + /** + * Zip two sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into a {@link Tuple2}. + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source1 The first {@link Publisher} source to zip. + * @param source2 The second {@link Publisher} source to zip. + * @param type of the value from source1 + * @param type of the value from source2 + * + * @return a zipped {@link Flux} + */ + public static Flux> zip(Publisher source1, Publisher source2) { + return zip(source1, source2, tuple2Function()); + } + + /** + * Zip three sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into a {@link Tuple3}. + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source1 The first upstream {@link Publisher} to subscribe to. + * @param source2 The second upstream {@link Publisher} to subscribe to. + * @param source3 The third upstream {@link Publisher} to subscribe to. + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * + * @return a zipped {@link Flux} + */ + public static Flux> zip(Publisher source1, + Publisher source2, + Publisher source3) { + return zip(Tuples.fn3(), source1, source2, source3); + } + + /** + * Zip four sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into a {@link Tuple4}. + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source1 The first upstream {@link Publisher} to subscribe to. + * @param source2 The second upstream {@link Publisher} to subscribe to. + * @param source3 The third upstream {@link Publisher} to subscribe to. + * @param source4 The fourth upstream {@link Publisher} to subscribe to. + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * @param type of the value from source4 + * + * @return a zipped {@link Flux} + */ + public static Flux> zip(Publisher source1, + Publisher source2, + Publisher source3, + Publisher source4) { + return zip(Tuples.fn4(), source1, source2, source3, source4); + } + + /** + * Zip five sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into a {@link Tuple5}. + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source1 The first upstream {@link Publisher} to subscribe to. + * @param source2 The second upstream {@link Publisher} to subscribe to. + * @param source3 The third upstream {@link Publisher} to subscribe to. + * @param source4 The fourth upstream {@link Publisher} to subscribe to. + * @param source5 The fifth upstream {@link Publisher} to subscribe to. + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * @param type of the value from source4 + * @param type of the value from source5 + * + * @return a zipped {@link Flux} + */ + public static Flux> zip(Publisher source1, + Publisher source2, + Publisher source3, + Publisher source4, + Publisher source5) { + return zip(Tuples.fn5(), source1, source2, source3, source4, source5); + } + + /** + * Zip six sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into a {@link Tuple6}. + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source1 The first upstream {@link Publisher} to subscribe to. + * @param source2 The second upstream {@link Publisher} to subscribe to. + * @param source3 The third upstream {@link Publisher} to subscribe to. + * @param source4 The fourth upstream {@link Publisher} to subscribe to. + * @param source5 The fifth upstream {@link Publisher} to subscribe to. + * @param source6 The sixth upstream {@link Publisher} to subscribe to. + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * @param type of the value from source4 + * @param type of the value from source5 + * @param type of the value from source6 + * + * @return a zipped {@link Flux} + */ + public static Flux> zip(Publisher source1, + Publisher source2, + Publisher source3, + Publisher source4, + Publisher source5, + Publisher source6) { + return zip(Tuples.fn6(), source1, source2, source3, source4, source5, source6); + } + + /** + * Zip seven sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into a {@link Tuple7}. + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source1 The first upstream {@link Publisher} to subscribe to. + * @param source2 The second upstream {@link Publisher} to subscribe to. + * @param source3 The third upstream {@link Publisher} to subscribe to. + * @param source4 The fourth upstream {@link Publisher} to subscribe to. + * @param source5 The fifth upstream {@link Publisher} to subscribe to. + * @param source6 The sixth upstream {@link Publisher} to subscribe to. + * @param source7 The seventh upstream {@link Publisher} to subscribe to. + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * @param type of the value from source4 + * @param type of the value from source5 + * @param type of the value from source6 + * @param type of the value from source7 + * + * @return a zipped {@link Flux} + */ + public static Flux> zip(Publisher source1, + Publisher source2, + Publisher source3, + Publisher source4, + Publisher source5, + Publisher source6, + Publisher source7) { + return zip(Tuples.fn7(), source1, source2, source3, source4, source5, source6, source7); + } + + /** + * Zip eight sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into a {@link Tuple8}. + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source1 The first upstream {@link Publisher} to subscribe to. + * @param source2 The second upstream {@link Publisher} to subscribe to. + * @param source3 The third upstream {@link Publisher} to subscribe to. + * @param source4 The fourth upstream {@link Publisher} to subscribe to. + * @param source5 The fifth upstream {@link Publisher} to subscribe to. + * @param source6 The sixth upstream {@link Publisher} to subscribe to. + * @param source7 The seventh upstream {@link Publisher} to subscribe to. + * @param source8 The eight upstream {@link Publisher} to subscribe to. + * @param type of the value from source1 + * @param type of the value from source2 + * @param type of the value from source3 + * @param type of the value from source4 + * @param type of the value from source5 + * @param type of the value from source6 + * @param type of the value from source7 + * @param type of the value from source8 + * + * @return a zipped {@link Flux} + */ + public static Flux> zip(Publisher source1, + Publisher source2, + Publisher source3, + Publisher source4, + Publisher source5, + Publisher source6, + Publisher source7, + Publisher source8) { + return zip(Tuples.fn8(), source1, source2, source3, source4, source5, source6, source7, source8); + } + + /** + * Zip multiple sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into an output value (constructed by the provided + * combinator). + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + * + * The {@link Iterable#iterator()} will be called on each {@link Publisher#subscribe(Subscriber)}. + * + *

+ * + * + * @param sources the {@link Iterable} providing sources to zip + * @param combinator The aggregate function that will receive a unique value from each upstream and return the value + * to signal downstream + * @param the combined produced type + * + * @return a zipped {@link Flux} + */ + public static Flux zip(Iterable> sources, + final Function combinator) { + + return zip(sources, Queues.XS_BUFFER_SIZE, combinator); + } + + /** + * Zip multiple sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into an output value (constructed by the provided + * combinator). + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + * + * The {@link Iterable#iterator()} will be called on each {@link Publisher#subscribe(Subscriber)}. + * + *

+ * + * + * @param sources the {@link Iterable} providing sources to zip + * @param prefetch the inner source request size + * @param combinator The aggregate function that will receive a unique value from each upstream and return the value + * to signal downstream + * @param the combined produced type + * + * @return a zipped {@link Flux} + */ + public static Flux zip(Iterable> sources, + int prefetch, + final Function combinator) { + + return onAssembly(new FluxZip(sources, + combinator, + Queues.get(prefetch), + prefetch)); + } + + /** + * Zip multiple sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into an output value (constructed by the provided + * combinator). + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param combinator The aggregate function that will receive a unique value from each upstream and return the + * value to signal downstream + * @param sources the array providing sources to zip + * @param the type of the input sources + * @param the combined produced type + * + * @return a zipped {@link Flux} + */ + @SafeVarargs + public static Flux zip( + final Function combinator, Publisher... sources) { + return zip(combinator, Queues.XS_BUFFER_SIZE, sources); + } + + /** + * Zip multiple sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into an output value (constructed by the provided + * combinator). + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param combinator The aggregate function that will receive a unique value from each upstream and return the + * value to signal downstream + * @param prefetch individual source request size + * @param sources the array providing sources to zip + * @param the type of the input sources + * @param the combined produced type + * + * @return a zipped {@link Flux} + */ + @SafeVarargs + public static Flux zip(final Function combinator, + int prefetch, + Publisher... sources) { + + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + Publisher source = sources[0]; + if (source instanceof Fuseable) { + return onAssembly(new FluxMapFuseable<>(from(source), + v -> combinator.apply(new Object[]{v}))); + } + return onAssembly(new FluxMap<>(from(source), + v -> combinator.apply(new Object[]{v}))); + } + + return onAssembly(new FluxZip<>(sources, + combinator, + Queues.get(prefetch), + prefetch)); + } + + /** + * Zip multiple sources together, that is to say wait for all the sources to emit one + * element and combine these elements once into an output value (constructed by the provided + * combinator). + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * Note that the {@link Publisher} sources from the outer {@link Publisher} will + * accumulate into an exhaustive list before starting zip operation. + *

+ * + * + * @param sources The {@link Publisher} of {@link Publisher} sources to zip. A finite publisher is required. + * @param combinator The aggregate function that will receive a unique value from each upstream and return the value + * to signal downstream + * @param the raw tuple type + * @param The produced output after transformation by the given combinator + * + * @return a {@link Flux} based on the produced value + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Flux zip(Publisher> sources, + final Function combinator) { + + return onAssembly(new FluxBuffer<>(from(sources), Integer.MAX_VALUE, listSupplier()) + .flatMap(new Function>, Publisher>() { + @Override + public Publisher apply(List> publishers) { + return zip(Tuples.fnAny((Function) + combinator), publishers.toArray(new Publisher[publishers + .size()])); + } + })); + } + + /** + * + * Emit a single boolean true if all values of this sequence match + * the {@link Predicate}. + *

+ * The implementation uses short-circuit logic and completes with false if + * the predicate doesn't match a value. + * + *

+ * + * + * @param predicate the {@link Predicate} that needs to apply to all emitted items + * + * @return a new {@link Mono} with true if all values satisfies a predicate and false + * otherwise + */ + public final Mono all(Predicate predicate) { + return Mono.onAssembly(new MonoAll<>(this, predicate)); + } + + /** + * Emit a single boolean true if any of the values of this {@link Flux} sequence match + * the predicate. + *

+ * The implementation uses short-circuit logic and completes with true if + * the predicate matches a value. + * + *

+ * + * + * @param predicate the {@link Predicate} that needs to apply to at least one emitted item + * + * @return a new {@link Mono} with true if any value satisfies a predicate and false + * otherwise + */ + public final Mono any(Predicate predicate) { + return Mono.onAssembly(new MonoAny<>(this, predicate)); + } + + /** + * Transform this {@link Flux} into a target type. + *

+	 * {@code flux.as(Mono::from).subscribe() }
+	 * 
+ * + * @param transformer the {@link Function} to immediately map this {@link Flux} + * into a target type instance. + * @param

the returned instance type + * + * @return the {@link Flux} transformed to an instance of P + * @see #transformDeferred(Function) transformDeferred(Function) for a lazy transformation of Flux + */ + public final

P as(Function, P> transformer) { + return transformer.apply(this); + } + + /** + * Subscribe to this {@link Flux} and block indefinitely + * until the upstream signals its first value or completes. Returns that value, + * or null if the Flux completes empty. In case the Flux errors, the original + * exception is thrown (wrapped in a {@link RuntimeException} if it was a checked + * exception). + *

+ * Note that each blockFirst() will trigger a new subscription: in other words, + * the result might miss signal from hot publishers. + * + *

+ * + * + * @return the first value or null + */ + @Nullable + public final T blockFirst() { + BlockingFirstSubscriber subscriber = new BlockingFirstSubscriber<>(); + subscribe((Subscriber) subscriber); + return subscriber.blockingGet(); + } + + /** + * Subscribe to this {@link Flux} and block until the upstream + * signals its first value, completes or a timeout expires. Returns that value, + * or null if the Flux completes empty. In case the Flux errors, the original + * exception is thrown (wrapped in a {@link RuntimeException} if it was a checked + * exception). If the provided timeout expires, a {@link RuntimeException} is thrown. + *

+ * Note that each blockFirst() will trigger a new subscription: in other words, + * the result might miss signal from hot publishers. + * + *

+ * + * + * @param timeout maximum time period to wait for before raising a {@link RuntimeException} + * @return the first value or null + */ + @Nullable + public final T blockFirst(Duration timeout) { + BlockingFirstSubscriber subscriber = new BlockingFirstSubscriber<>(); + subscribe((Subscriber) subscriber); + return subscriber.blockingGet(timeout.toNanos(), TimeUnit.NANOSECONDS); + } + + /** + * Subscribe to this {@link Flux} and block indefinitely + * until the upstream signals its last value or completes. Returns that value, + * or null if the Flux completes empty. In case the Flux errors, the original + * exception is thrown (wrapped in a {@link RuntimeException} if it was a checked + * exception). + *

+ * Note that each blockLast() will trigger a new subscription: in other words, + * the result might miss signal from hot publishers. + * + *

+ * + * + * @return the last value or null + */ + @Nullable + public final T blockLast() { + BlockingLastSubscriber subscriber = new BlockingLastSubscriber<>(); + subscribe((Subscriber) subscriber); + return subscriber.blockingGet(); + } + + + /** + * Subscribe to this {@link Flux} and block until the upstream + * signals its last value, completes or a timeout expires. Returns that value, + * or null if the Flux completes empty. In case the Flux errors, the original + * exception is thrown (wrapped in a {@link RuntimeException} if it was a checked + * exception). If the provided timeout expires, a {@link RuntimeException} is thrown. + *

+ * Note that each blockLast() will trigger a new subscription: in other words, + * the result might miss signal from hot publishers. + * + *

+ * + * + * @param timeout maximum time period to wait for before raising a {@link RuntimeException} + * @return the last value or null + */ + @Nullable + public final T blockLast(Duration timeout) { + BlockingLastSubscriber subscriber = new BlockingLastSubscriber<>(); + subscribe((Subscriber) subscriber); + return subscriber.blockingGet(timeout.toNanos(), TimeUnit.NANOSECONDS); + } + + /** + * Collect all incoming values into a single {@link List} buffer that will be emitted + * by the returned {@link Flux} once this Flux completes. + *

+ * + * + *

Discard Support: This operator discards the buffer upon cancellation or error triggered by a data signal. + * + * @return a buffered {@link Flux} of at most one {@link List} + * @see #collectList() for an alternative collecting algorithm returning {@link Mono} + */ + public final Flux> buffer() { + return buffer(Integer.MAX_VALUE); + } + + /** + * Collect incoming values into multiple {@link List} buffers that will be emitted + * by the returned {@link Flux} each time the given max size is reached or once this + * Flux completes. + *

+ * + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * + * @param maxSize the maximum collected size + * + * @return a microbatched {@link Flux} of {@link List} + */ + public final Flux> buffer(int maxSize) { + return buffer(maxSize, listSupplier()); + } + + /** + * Collect incoming values into multiple user-defined {@link Collection} buffers that + * will be emitted by the returned {@link Flux} each time the given max size is reached + * or once this Flux completes. + *

+ * + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal, + * as well as latest unbuffered element if the bufferSupplier fails. + * + * @param maxSize the maximum collected size + * @param bufferSupplier a {@link Supplier} of the concrete {@link Collection} to use for each buffer + * @param the {@link Collection} buffer type + * + * @return a microbatched {@link Flux} of {@link Collection} + */ + public final > Flux buffer(int maxSize, Supplier bufferSupplier) { + return onAssembly(new FluxBuffer<>(this, maxSize, bufferSupplier)); + } + + /** + * Collect incoming values into multiple {@link List} buffers that will be emitted + * by the returned {@link Flux} each time the given max size is reached or once this + * Flux completes. Buffers can be created with gaps, as a new buffer will be created + * every time {@code skip} values have been emitted by the source. + *

+ * When maxSize < skip : dropping buffers + *

+ * + *

+ * When maxSize > skip : overlapping buffers + *

+ * + *

+ * When maxSize == skip : exact buffers + *

+ * + * + *

Discard Support: This operator discards elements in between buffers (in the case of + * dropping buffers). It also discards the currently open buffer upon cancellation or error triggered by a data signal. + * Note however that overlapping buffer variant DOES NOT discard, as this might result in an element + * being discarded from an early buffer while it is still valid in a more recent buffer. + * + * @param skip the number of items to count before creating a new buffer + * @param maxSize the max collected size + * + * @return a microbatched {@link Flux} of possibly overlapped or gapped {@link List} + */ + public final Flux> buffer(int maxSize, int skip) { + return buffer(maxSize, skip, listSupplier()); + } + + /** + * Collect incoming values into multiple user-defined {@link Collection} buffers that + * will be emitted by the returned {@link Flux} each time the given max size is reached + * or once this Flux completes. Buffers can be created with gaps, as a new buffer will + * be created every time {@code skip} values have been emitted by the source + *

+ * When maxSize < skip : dropping buffers + *

+ * + *

+ * When maxSize > skip : overlapping buffers + *

+ * + *

+ * When maxSize == skip : exact buffers + *

+ * + * + *

Discard Support: This operator discards elements in between buffers (in the case of + * dropping buffers). It also discards the currently open buffer upon cancellation or error triggered by a data signal. + * Note however that overlapping buffer variant DOES NOT discard, as this might result in an element + * being discarded from an early buffer while it is still valid in a more recent buffer. + * + * @param skip the number of items to count before creating a new buffer + * @param maxSize the max collected size + * @param bufferSupplier a {@link Supplier} of the concrete {@link Collection} to use for each buffer + * @param the {@link Collection} buffer type + * + * @return a microbatched {@link Flux} of possibly overlapped or gapped + * {@link Collection} + */ + public final > Flux buffer(int maxSize, + int skip, Supplier bufferSupplier) { + return onAssembly(new FluxBuffer<>(this, maxSize, skip, bufferSupplier)); + } + + /** + * Collect incoming values into multiple {@link List} buffers, as delimited by the + * signals of a companion {@link Publisher} this operator will subscribe to. + *

+ * + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * + * @param other the companion {@link Publisher} whose signals trigger new buffers + * + * @return a microbatched {@link Flux} of {@link List} delimited by signals from a {@link Publisher} + */ + public final Flux> buffer(Publisher other) { + return buffer(other, listSupplier()); + } + + /** + * Collect incoming values into multiple user-defined {@link Collection} buffers, as + * delimited by the signals of a companion {@link Publisher} this operator will + * subscribe to. + *

+ * + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal, + * and the last received element when the bufferSupplier fails. + * + * @param other the companion {@link Publisher} whose signals trigger new buffers + * @param bufferSupplier a {@link Supplier} of the concrete {@link Collection} to use for each buffer + * @param the {@link Collection} buffer type + * + * @return a microbatched {@link Flux} of {@link Collection} delimited by signals from a {@link Publisher} + */ + public final > Flux buffer(Publisher other, Supplier bufferSupplier) { + return onAssembly(new FluxBufferBoundary<>(this, other, bufferSupplier)); + } + + /** + * Collect incoming values into multiple {@link List} buffers that will be emitted by + * the returned {@link Flux} every {@code bufferingTimespan}. + *

+ * + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * + * @param bufferingTimespan the duration from buffer creation until a buffer is closed and emitted + * + * @return a microbatched {@link Flux} of {@link List} delimited by the given time span + */ + public final Flux> buffer(Duration bufferingTimespan) { + return buffer(bufferingTimespan, Schedulers.parallel()); + } + + /** + * Collect incoming values into multiple {@link List} buffers created at a given + * {@code openBufferEvery} period. Each buffer will last until the {@code bufferingTimespan} has elapsed, + * thus emitting the bucket in the resulting {@link Flux}. + *

+ * When bufferingTimespan < openBufferEvery : dropping buffers + *

+ * + *

+ * When bufferingTimespan > openBufferEvery : overlapping buffers + *

+ * + *

+ * When bufferingTimespan == openBufferEvery : exact buffers + *

+ * + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * It DOES NOT provide strong guarantees in the case of overlapping buffers, as elements + * might get discarded too early (from the first of two overlapping buffers for instance). + * + * @param bufferingTimespan the duration from buffer creation until a buffer is closed and emitted + * @param openBufferEvery the interval at which to create a new buffer + * + * @return a microbatched {@link Flux} of {@link List} delimited by the given period openBufferEvery and sized by bufferingTimespan + */ + public final Flux> buffer(Duration bufferingTimespan, Duration openBufferEvery) { + return buffer(bufferingTimespan, openBufferEvery, Schedulers.parallel()); + } + + /** + * Collect incoming values into multiple {@link List} buffers that will be emitted by + * the returned {@link Flux} every {@code bufferingTimespan}, 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 bufferingTimespan the duration from buffer creation until a buffer is closed and emitted + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a microbatched {@link Flux} of {@link List} delimited by the given period + */ + public final Flux> buffer(Duration bufferingTimespan, Scheduler timer) { + return buffer(interval(bufferingTimespan, timer)); + } + + /** + * Collect incoming values into multiple {@link List} buffers created at a given + * {@code openBufferEvery} period, as measured on the provided {@link Scheduler}. Each + * buffer will last until the {@code bufferingTimespan} has elapsed (also measured on the scheduler), + * thus emitting the bucket in the resulting {@link Flux}. + *

+ * When bufferingTimespan < openBufferEvery : dropping buffers + *

+ * + *

+ * When bufferingTimespan > openBufferEvery : overlapping buffers + *

+ * + *

+ * When bufferingTimespan == openBufferEvery : exact buffers + *

+ * + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * It DOES NOT provide strong guarantees in the case of overlapping buffers, as elements + * might get discarded too early (from the first of two overlapping buffers for instance). + * + * @param bufferingTimespan the duration from buffer creation until a buffer is closed and emitted + * @param openBufferEvery the interval at which to create a new buffer + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a microbatched {@link Flux} of {@link List} delimited by the given period openBufferEvery and sized by bufferingTimespan + */ + public final Flux> buffer(Duration bufferingTimespan, Duration openBufferEvery, Scheduler timer) { + if (bufferingTimespan.equals(openBufferEvery)) { + return buffer(bufferingTimespan, timer); + } + return bufferWhen(interval(Duration.ZERO, openBufferEvery, timer), aLong -> Mono + .delay(bufferingTimespan, timer)); + } + + /** + * 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 + * + * @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) { + return bufferTimeout(maxSize, maxTime, listSupplier()); + } + + /** + * 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 + * + * @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) { + return bufferTimeout(maxSize, maxTime, Schedulers.parallel(), + bufferSupplier); + } + + /** + * 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 + * + * @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) { + return bufferTimeout(maxSize, maxTime, timer, listSupplier()); + } + + /** + * 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 + * + * @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) { + return onAssembly(new FluxBufferTimeout<>(this, maxSize, maxTime.toNanos(), TimeUnit.NANOSECONDS, timer, bufferSupplier)); + } + + /** + * 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) + * is included as last element in the emitted buffer. + *

+ * + *

+ * On completion, if the latest buffer is non-empty and has not been closed it is + * emitted. However, such a "partial" buffer isn't emitted in case of onError + * termination. + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * + * @param predicate a predicate that triggers the next buffer when it becomes true. + * + * @return a microbatched {@link Flux} of {@link List} + */ + public final Flux> bufferUntil(Predicate predicate) { + return onAssembly(new FluxBufferPredicate<>(this, predicate, + listSupplier(), FluxBufferPredicate.Mode.UNTIL)); + } + + /** + * 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 buffer into which the element that triggers the predicate to return true + * (and thus closes a buffer) is included depends on the {@code cutBefore} parameter: + * set it to true to include the boundary element in the newly opened buffer, false to + * include it in the closed buffer (as in {@link #bufferUntil(Predicate)}). + *

+ * + *

+ * On completion, if the latest buffer is non-empty and has not been closed it is + * emitted. However, such a "partial" buffer isn't emitted in case of onError + * termination. + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * + * @param predicate a predicate that triggers the next buffer when it becomes true. + * @param cutBefore set to true to include the triggering element in the new buffer rather than the old. + * + * @return a microbatched {@link Flux} of {@link List} + */ + public final Flux> bufferUntil(Predicate predicate, boolean cutBefore) { + return onAssembly(new FluxBufferPredicate<>(this, predicate, listSupplier(), + cutBefore ? FluxBufferPredicate.Mode.UNTIL_CUT_BEFORE + : FluxBufferPredicate.Mode.UNTIL)); + } + + /** + * Collect subsequent repetitions of an element (that is, if they arrive right after + * one another) into multiple {@link List} buffers that will be emitted by the + * resulting {@link Flux}. + * + *

+ * + *

+ * + * @return a microbatched {@link Flux} of {@link List} + */ + public final Flux> bufferUntilChanged() { + return bufferUntilChanged(identityFunction()); + } + + /** + * Collect subsequent repetitions of an element (that is, if they arrive right after + * one another), as compared by a key extracted through the user provided {@link + * Function}, into multiple {@link List} buffers that will be emitted by the + * resulting {@link Flux}. + * + *

+ * + *

+ * + * @param keySelector function to compute comparison key for each element + * @return a microbatched {@link Flux} of {@link List} + */ + public final Flux> bufferUntilChanged(Function keySelector) { + return bufferUntilChanged(keySelector, equalPredicate()); + } + + /** + * Collect subsequent repetitions of an element (that is, if they arrive right after + * one another), as compared by a key extracted through the user provided {@link + * Function} and compared using a supplied {@link BiPredicate}, into multiple + * {@link List} buffers that will be emitted by the resulting {@link Flux}. + * + *

+ * + *

+ * + * @param keySelector function to compute comparison key for each element + * @param keyComparator predicate used to compare keys + * @return a microbatched {@link Flux} of {@link List} + */ + public final Flux> bufferUntilChanged(Function keySelector, + BiPredicate keyComparator) { + return Flux.defer(() -> bufferUntil(new FluxBufferPredicate.ChangedPredicate(keySelector, + keyComparator), true)); + } + + /** + * Collect incoming values into multiple {@link List} buffers that will be emitted by + * the resulting {@link Flux}. Each buffer continues aggregating values while the + * given predicate returns true, and a new buffer is created as soon as the + * predicate returns false... Note that the element that triggers the predicate + * to return false (and thus closes a buffer) is NOT included in any emitted buffer. + *

+ * + *

+ * On completion, if the latest buffer is non-empty and has not been closed it is + * emitted. However, such a "partial" buffer isn't emitted in case of onError + * termination. + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal, + * as well as the buffer-triggering element. + * + * @param predicate a predicate that triggers the next buffer when it becomes false. + * + * @return a microbatched {@link Flux} of {@link List} + */ + public final Flux> bufferWhile(Predicate predicate) { + return onAssembly(new FluxBufferPredicate<>(this, predicate, + listSupplier(), FluxBufferPredicate.Mode.WHILE)); + } + + /** + * Collect incoming values into multiple {@link List} buffers started each time an opening + * companion {@link Publisher} emits. Each buffer will last until the corresponding + * closing companion {@link Publisher} emits, thus releasing the buffer to the resulting {@link Flux}. + *

+ * When Open signal is strictly not overlapping Close signal : dropping buffers (see green marbles in diagram below). + *

+ * When Open signal is strictly more frequent than Close signal : overlapping buffers (see second and third buffers in diagram below). + *

+ * When Open signal is exactly coordinated with Close signal : exact buffers + *

+ * + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * It DOES NOT provide strong guarantees in the case of overlapping buffers, as elements + * might get discarded too early (from the first of two overlapping buffers for instance). + * + * @param bucketOpening a companion {@link Publisher} to subscribe for buffer creation signals. + * @param closeSelector a factory that, given a buffer opening signal, returns a companion + * {@link Publisher} to subscribe to for buffer closure and emission signals. + * @param the element type of the buffer-opening sequence + * @param the element type of the buffer-closing sequence + * + * @return a microbatched {@link Flux} of {@link List} delimited by an opening {@link Publisher} and a relative + * closing {@link Publisher} + */ + public final Flux> bufferWhen(Publisher bucketOpening, + Function> closeSelector) { + return bufferWhen(bucketOpening, closeSelector, listSupplier()); + } + + /** + * Collect incoming values into multiple user-defined {@link Collection} buffers started each time an opening + * companion {@link Publisher} emits. Each buffer will last until the corresponding + * closing companion {@link Publisher} emits, thus releasing the buffer to the resulting {@link Flux}. + *

+ * When Open signal is strictly not overlapping Close signal : dropping buffers (see green marbles in diagram below). + *

+ * When Open signal is strictly more frequent than Close signal : overlapping buffers (see second and third buffers in diagram below). + *

+ * + * + *

Discard Support: This operator discards the currently open buffer upon cancellation or error triggered by a data signal. + * It DOES NOT provide strong guarantees in the case of overlapping buffers, as elements + * might get discarded too early (from the first of two overlapping buffers for instance). + * + * @param bucketOpening a companion {@link Publisher} to subscribe for buffer creation signals. + * @param closeSelector a factory that, given a buffer opening signal, returns a companion + * {@link Publisher} to subscribe to for buffer closure and emission signals. + * @param bufferSupplier a {@link Supplier} of the concrete {@link Collection} to use for each buffer + * @param the element type of the buffer-opening sequence + * @param the element type of the buffer-closing sequence + * @param the {@link Collection} buffer type + * + * @return a microbatched {@link Flux} of {@link Collection} delimited by an opening {@link Publisher} and a relative + * closing {@link Publisher} + */ + public final > Flux bufferWhen(Publisher bucketOpening, + Function> closeSelector, Supplier bufferSupplier) { + return onAssembly(new FluxBufferWhen<>(this, bucketOpening, closeSelector, + bufferSupplier, Queues.unbounded(Queues.XS_BUFFER_SIZE))); + } + + /** + * Turn this {@link Flux} into a hot source and cache last emitted signals for further {@link Subscriber}. Will + * retain an unbounded volume of onNext signals. Completion and Error will also be + * replayed. + *

+ * + * + * @return a replaying {@link Flux} + */ + public final Flux cache() { + return cache(Integer.MAX_VALUE); + } + + /** + * Turn this {@link Flux} into a hot source and cache last emitted signals for further {@link Subscriber}. + * 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 + * expiration. + *

+ * + * + * @param history number of elements retained in cache + * + * @return a replaying {@link Flux} + * + */ + public final Flux cache(int history) { + return replay(history).autoConnect(); + } + + /** + * Turn this {@link Flux} into a hot source and cache last emitted signals for further + * {@link Subscriber}. Will retain an unbounded history but apply a per-item expiry timeout + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription. + *

+ * + * + * @param ttl Time-to-live for each cached item and post termination. + * + * @return a replaying {@link Flux} + */ + public final Flux cache(Duration ttl) { + return cache(ttl, Schedulers.parallel()); + } + + /** + * Turn this {@link Flux} into a hot source and cache last emitted signals for further + * {@link Subscriber}. Will retain an unbounded history but apply a per-item expiry timeout + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription. + *

+ * + * + * @param ttl Time-to-live for each cached item and post termination. + * @param timer the {@link Scheduler} on which to measure the duration. + * + * @return a replaying {@link Flux} + */ + public final Flux cache(Duration ttl, Scheduler timer) { + return cache(Integer.MAX_VALUE, ttl, timer); + } + + /** + * Turn this {@link Flux} into a hot source and cache last emitted signals for further + * {@link Subscriber}. Will retain up to the given history size and apply a per-item expiry + * timeout. + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription. + *

+ * + * + * @param history number of elements retained in cache + * @param ttl Time-to-live for each cached item and post termination. + * + * @return a replaying {@link Flux} + */ + public final Flux cache(int history, Duration ttl) { + return cache(history, ttl, Schedulers.parallel()); + } + + /** + * Turn this {@link Flux} into a hot source and cache last emitted signals for further + * {@link Subscriber}. Will retain up to the given history size and apply a per-item expiry + * timeout. + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription. + *

+ * + * + * @param history number of elements retained in cache + * @param ttl Time-to-live for each cached item and post termination. + * @param timer the {@link Scheduler} on which to measure the duration. + * + * @return a replaying {@link Flux} + */ + public final Flux cache(int history, Duration ttl, Scheduler timer) { + return replay(history, ttl, timer).autoConnect(); + } + + /** + * Cast the current {@link Flux} produced type into a target produced type. + * + *

+ * + * + * @param the {@link Flux} output type + * @param clazz the target class to cast to + * + * @return a casted {@link Flux} + */ + public final Flux cast(Class clazz) { + Objects.requireNonNull(clazz, "clazz"); + return map(clazz::cast); + } + + /** + * Prepare this {@link Flux} so that subscribers will cancel from it on a + * specified + * {@link Scheduler}. + * + *

+ * + * + * @param scheduler the {@link Scheduler} to signal cancel on + * + * @return a scheduled cancel {@link Flux} + */ + public final Flux cancelOn(Scheduler scheduler) { + return onAssembly(new FluxCancelOn<>(this, scheduler)); + } + + /** + * Activate traceback (full assembly tracing) for this particular {@link Flux}, in case of an error + * upstream of the checkpoint. Tracing incurs the cost of an exception stack trace + * 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. + *

+ * 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 + * would appear as a component of the composite. In any case, the traceback nature can be detected via + * {@link Exceptions#isTraceback(Throwable)}. + * + * @return the assembly tracing {@link Flux}. + */ + public final Flux checkpoint() { + return checkpoint(null, true); + } + + /** + * Activate traceback (assembly marker) for this particular {@link Flux} by giving it a description that + * will be reflected in the assembly traceback in case of an error upstream of the + * checkpoint. Note that unlike {@link #checkpoint()}, this doesn't create a + * filled stack trace, avoiding the main cost of the operator. + * However, as a trade-off the description must be unique enough for the user to find + * out where this Flux was assembled. If you only want a generic description, and + * still rely on the stack trace to find the assembly site, use the + * {@link #checkpoint(String, boolean)} variant. + *

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

+ * 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 + * would appear as a component of the composite. In any case, the traceback nature can be detected via + * {@link Exceptions#isTraceback(Throwable)}. + * + * @param description a unique enough description to include in the light assembly traceback. + * @return the assembly marked {@link Flux} + */ + public final Flux checkpoint(String description) { + return checkpoint(Objects.requireNonNull(description), false); + } + + /** + * Activate traceback (full assembly tracing or the lighter assembly marking depending on the + * {@code forceStackTrace} option). + *

+ * By setting the {@code forceStackTrace} parameter to {@literal true}, activate assembly + * tracing for this particular {@link Flux} and give it a description that + * will be reflected in the assembly traceback in case of an error upstream of the + * checkpoint. Note that unlike {@link #checkpoint(String)}, this will incur + * the cost of an exception stack trace creation. The description could for + * example be a meaningful name for the assembled flux or a wider correlation ID, + * since the stack trace will always provide enough information to locate where this + * Flux was assembled. + *

+ * By setting {@code forceStackTrace} to {@literal false}, behaves like + * {@link #checkpoint(String)} and is subject to the same caveat in choosing the + * description. + *

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

+ * 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 + * would appear as a component of the composite. In any case, the traceback nature can be detected via + * {@link Exceptions#isTraceback(Throwable)}. + * + * @param description a description (must be unique enough if forceStackTrace is set + * to false). + * @param forceStackTrace false to make a light checkpoint without a stacktrace, true + * to use a stack trace. + * @return the assembly marked {@link Flux}. + */ + public final Flux checkpoint(@Nullable String description, boolean forceStackTrace) { + final AssemblySnapshot stacktrace; + if (!forceStackTrace) { + stacktrace = new CheckpointLightSnapshot(description); + } + else { + stacktrace = new CheckpointHeavySnapshot(description, Traces.callSiteSupplierFactory.get()); + } + + return new FluxOnAssembly<>(this, stacktrace); + } + + /** + * Collect all elements emitted by this {@link Flux} into a user-defined container, + * by applying a collector {@link BiConsumer} taking the container and each element. + * The collected result will be emitted when this sequence completes, emitting the + * empty container if the sequence was empty. + * + *

+ * + * + *

Discard Support: This operator discards the container upon cancellation or error triggered by a data signal. + * 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 collector {@link BiConsumer} fails + * to accumulate an element, the container is discarded as above and the triggering element is also discarded. + * + * @param the container type + * @param containerSupplier the supplier of the container instance for each Subscriber + * @param collector a consumer of both the container instance and the value being currently collected + * + * @return a {@link Mono} of the collected container on complete + * + */ + public final Mono collect(Supplier containerSupplier, BiConsumer collector) { + return Mono.onAssembly(new MonoCollect<>(this, containerSupplier, collector)); + } + + /** + * Collect all elements emitted by this {@link Flux} into a container, + * by applying a Java 8 Stream API {@link Collector} + * The collected result will be emitted when this sequence completes, emitting + * the empty container if the sequence was empty. + * + *

+ * + * + *

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 + * an element into the intermediate container, the container is discarded as above and the triggering element + * is also discarded. + * + * @param collector the {@link Collector} + * @param The mutable accumulation type + * @param the container type + * + * @return a {@link Mono} of the collected container on complete + * + */ + public final Mono collect(Collector collector) { + return Mono.onAssembly(new MonoStreamCollector<>(this, collector)); + } + + /** + * Collect all elements emitted by this {@link Flux} into a {@link List} that is + * emitted by the resulting {@link Mono} when this sequence completes, emitting the + * empty {@link List} if the sequence was empty. + * + *

+ * + * + *

Discard Support: This operator discards the elements in the {@link List} upon + * cancellation or error triggered by a data signal. + * + * @return a {@link Mono} of a {@link List} of all values from this {@link Flux} + */ + public final Mono> collectList() { + 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.onAssembly(new MonoCallable<>(() -> { + List list = Flux.listSupplier().get(); + if (v != null) { + list.add(v); + } + return list; + })); + + } + @SuppressWarnings("unchecked") + Callable thiz = (Callable)this; + return Mono.onAssembly(new MonoCallable<>(() -> { + List list = Flux.listSupplier().get(); + T u = thiz.call(); + if (u != null) { + list.add(u); + } + return list; + })); + } + return Mono.onAssembly(new MonoCollectList<>(this)); + } + + /** + * Collect all elements emitted by this {@link Flux} into a hashed {@link Map} that is + * emitted by the resulting {@link Mono} when this sequence completes, emitting the + * empty {@link Map} if the sequence was empty. + * The key is extracted from each element by applying the {@code keyExtractor} + * {@link Function}. In case several elements map to the same key, the associated value + * will be the most recently emitted element. + * + *

+ * + * + *

Discard Support: This operator discards the whole {@link Map} upon cancellation or error + * triggered by a data signal, so discard handlers will have to unpack the map. + * + * @param keyExtractor a {@link Function} to map elements to a key for the {@link Map} + * @param the type of the key extracted from each source element + * + * @return a {@link Mono} of a {@link Map} of key-element pairs (only including latest + * element in case of key conflicts) + */ + public final Mono> collectMap(Function keyExtractor) { + return collectMap(keyExtractor, identityFunction()); + } + + /** + * Collect all elements emitted by this {@link Flux} into a hashed {@link Map} that is + * emitted by the resulting {@link Mono} when this sequence completes, emitting the + * empty {@link Map} if the sequence was empty. + * The key is extracted from each element by applying the {@code keyExtractor} + * {@link Function}, and the value is extracted by the {@code valueExtractor} Function. + * In case several elements map to the same key, the associated value will be derived + * from the most recently emitted element. + * + *

+ * + * + *

Discard Support: This operator discards the whole {@link Map} upon cancellation or error + * triggered by a data signal, so discard handlers will have to unpack the map. + * + * @param keyExtractor a {@link Function} to map elements to a key for the {@link Map} + * @param valueExtractor a {@link Function} to map elements to a value for the {@link Map} + * + * @param the type of the key extracted from each source element + * @param the type of the value extracted from each source element + * + * @return a {@link Mono} of a {@link Map} of key-element pairs (only including latest + * element's value in case of key conflicts) + */ + public final Mono> collectMap(Function keyExtractor, + Function valueExtractor) { + return collectMap(keyExtractor, valueExtractor, () -> new HashMap<>()); + } + + /** + * Collect all elements emitted by this {@link Flux} into a user-defined {@link Map} that is + * emitted by the resulting {@link Mono} when this sequence completes, emitting the + * empty {@link Map} if the sequence was empty. + * The key is extracted from each element by applying the {@code keyExtractor} + * {@link Function}, and the value is extracted by the {@code valueExtractor} Function. + * In case several elements map to the same key, the associated value will be derived + * from the most recently emitted element. + * + *

+ * + * + *

Discard Support: This operator discards the whole {@link Map} upon cancellation or error + * triggered by a data signal, so discard handlers will have to unpack the map. + * + * @param keyExtractor a {@link Function} to map elements to a key for the {@link Map} + * @param valueExtractor a {@link Function} to map elements to a value for the {@link Map} + * @param mapSupplier a {@link Map} factory called for each {@link Subscriber} + * + * @param the type of the key extracted from each source element + * @param the type of the value extracted from each source element + * + * @return a {@link Mono} of a {@link Map} of key-value pairs (only including latest + * element's value in case of key conflicts) + */ + public final Mono> collectMap( + final Function keyExtractor, + final Function valueExtractor, + Supplier> mapSupplier) { + Objects.requireNonNull(keyExtractor, "Key extractor is null"); + Objects.requireNonNull(valueExtractor, "Value extractor is null"); + Objects.requireNonNull(mapSupplier, "Map supplier is null"); + return collect(mapSupplier, (m, d) -> m.put(keyExtractor.apply(d), valueExtractor.apply(d))); + } + + /** + * Collect all elements emitted by this {@link Flux} into a {@link Map multimap} that is + * emitted by the resulting {@link Mono} when this sequence completes, emitting the + * empty {@link Map multimap} if the sequence was empty. + * The key is extracted from each element by applying the {@code keyExtractor} + * {@link Function}, and every element mapping to the same key is stored in the {@link List} + * associated to said key. + * + *

+ * + * + *

Discard Support: This operator discards the whole {@link Map} upon cancellation or error + * triggered by a data signal, so discard handlers will have to unpack the list values in the map. + * + * @param keyExtractor a {@link Function} to map elements to a key for the {@link Map} + * + * @param the type of the key extracted from each source element + * + * @return a {@link Mono} of a {@link Map} of key-List(elements) pairs + */ + public final Mono>> collectMultimap(Function keyExtractor) { + return collectMultimap(keyExtractor, identityFunction()); + } + + /** + * Collect all elements emitted by this {@link Flux} into a {@link Map multimap} that is + * emitted by the resulting {@link Mono} when this sequence completes, emitting the + * empty {@link Map multimap} if the sequence was empty. + * The key is extracted from each element by applying the {@code keyExtractor} + * {@link Function}, and every element mapping to the same key is converted by the + * {@code valueExtractor} Function to a value stored in the {@link List} associated to + * said key. + * + *

+ * + * + *

Discard Support: This operator discards the whole {@link Map} upon cancellation or error + * triggered by a data signal, so discard handlers will have to unpack the list values in the map. + * + * @param keyExtractor a {@link Function} to map elements to a key for the {@link Map} + * @param valueExtractor a {@link Function} to map elements to a value for the {@link Map} + * + * @param the type of the key extracted from each source element + * @param the type of the value extracted from each source element + * + * @return a {@link Mono} of a {@link Map} of key-List(values) pairs + */ + public final Mono>> collectMultimap(Function keyExtractor, + Function valueExtractor) { + return collectMultimap(keyExtractor, valueExtractor, () -> new HashMap<>()); + } + + /** + * Collect all elements emitted by this {@link Flux} into a user-defined {@link Map multimap} that is + * emitted by the resulting {@link Mono} when this sequence completes, emitting the + * empty {@link Map multimap} if the sequence was empty. + * The key is extracted from each element by applying the {@code keyExtractor} + * {@link Function}, and every element mapping to the same key is converted by the + * {@code valueExtractor} Function to a value stored in the {@link Collection} associated to + * said key. + * + *

+ * + * + *

Discard Support: This operator discards the whole {@link Map} upon cancellation or error + * triggered by a data signal, so discard handlers will have to unpack the list values in the map. + * + * @param keyExtractor a {@link Function} to map elements to a key for the {@link Map} + * @param valueExtractor a {@link Function} to map elements to a value for the {@link Map} + * @param mapSupplier a multimap ({@link Map} of {@link Collection}) factory called + * for each {@link Subscriber} + * + * @param the type of the key extracted from each source element + * @param the type of the value extracted from each source element + * + * @return a {@link Mono} of a {@link Map} of key-Collection(values) pairs + * + */ + public final Mono>> collectMultimap( + final Function keyExtractor, + final Function valueExtractor, + Supplier>> mapSupplier) { + Objects.requireNonNull(keyExtractor, "Key extractor is null"); + Objects.requireNonNull(valueExtractor, "Value extractor is null"); + Objects.requireNonNull(mapSupplier, "Map supplier is null"); + return collect(mapSupplier, (m, d) -> { + K key = keyExtractor.apply(d); + Collection values = m.computeIfAbsent(key, k -> new ArrayList<>()); + values.add(valueExtractor.apply(d)); + }); + } + + /** + * Collect all elements emitted by this {@link Flux} until this sequence completes, + * and then sort them in natural order into a {@link List} that is emitted by the + * resulting {@link Mono}. If the sequence was empty, empty {@link List} will be emitted. + * + *

+ * + * + *

Discard Support: This operator is based on {@link #collectList()}, and as such discards the + * elements in the {@link List} individually upon cancellation or error triggered by a data signal. + * + * @return a {@link Mono} of a sorted {@link List} of all values from this {@link Flux}, in natural order + */ + public final Mono> collectSortedList() { + return collectSortedList(null); + } + + /** + * Collect all elements emitted by this {@link Flux} until this sequence completes, + * and then sort them using a {@link Comparator} into a {@link List} that is emitted + * by the resulting {@link Mono}. If the sequence was empty, empty {@link List} will be emitted. + * + *

+ * + * + *

Discard Support: This operator is based on {@link #collectList()}, and as such discards the + * elements in the {@link List} individually upon cancellation or error triggered by a data signal. + * + * @param comparator a {@link Comparator} to sort the items of this sequences + * + * @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 + list.sort(comparator); + }); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux}, sequentially and + * preserving order using concatenation. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMap(Function) flatMap} and {@link #flatMapSequential(Function) flatMapSequential}: + *

+ * + *

+ * Errors will immediately short circuit current concat backlog. + * + *

+ * + * + *

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 the produced concatenated type + * + * @return a concatenated {@link Flux} + */ + public final Flux concatMap(Function> + mapper) { + return concatMap(mapper, Queues.XS_BUFFER_SIZE); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux}, sequentially and + * preserving order using concatenation. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMap(Function) flatMap} and {@link #flatMapSequential(Function) flatMapSequential}: + *

    + *
  • Generation of inners and subscription: this operator waits for one + * inner to complete before generating the next one and subscribing to it.
  • + *
  • Ordering of the flattened values: this operator naturally preserves + * the same order as the source elements, concatenating the inners from each source + * element sequentially.
  • + *
  • Interleaving: this operator does not let values from different inners + * interleave (concatenation).
  • + *
+ * + *

+ * Errors will immediately short circuit current concat backlog. The prefetch argument + * allows to give an arbitrary prefetch size to the upstream source. + * + *

+ * + * + *

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 the produced concatenated type + * + * @return a concatenated {@link Flux} + */ + public final Flux concatMap(Function> + mapper, int prefetch) { + if (prefetch == 0) { + return onAssembly(new FluxConcatMapNoPrefetch<>(this, mapper, FluxConcatMap.ErrorMode.IMMEDIATE)); + } + return onAssembly(new FluxConcatMap<>(this, mapper, Queues.get(prefetch), prefetch, + FluxConcatMap.ErrorMode.IMMEDIATE)); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux}, sequentially and + * preserving order using concatenation. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMap(Function) flatMap} and {@link #flatMapSequential(Function) flatMapSequential}: + *

    + *
  • Generation of inners and subscription: this operator waits for one + * inner to complete before generating the next one and subscribing to it.
  • + *
  • Ordering of the flattened values: this operator naturally preserves + * the same order as the source elements, concatenating the inners from each source + * element sequentially.
  • + *
  • Interleaving: this operator does not let values from different inners + * interleave (concatenation).
  • + *
+ * + *

+ * 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} + * if several sources error. + * + *

+ * + * + *

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 the produced concatenated type + * + * @return a concatenated {@link Flux} + * + */ + public final Flux concatMapDelayError(Function> mapper) { + return concatMapDelayError(mapper, Queues.XS_BUFFER_SIZE); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux}, sequentially and + * preserving order using concatenation. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMap(Function) flatMap} and {@link #flatMapSequential(Function) flatMapSequential}: + *

    + *
  • Generation of inners and subscription: this operator waits for one + * inner to complete before generating the next one and subscribing to it.
  • + *
  • Ordering of the flattened values: this operator naturally preserves + * the same order as the source elements, concatenating the inners from each source + * element sequentially.
  • + *
  • Interleaving: this operator does not let values from different inners + * interleave (concatenation).
  • + *
+ * + *

+ * 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} + * if several sources error. + * The prefetch argument allows to give an arbitrary prefetch size to the upstream source. + * + *

+ * + * + *

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 the produced concatenated type + * + * @return a concatenated {@link Flux} + * + */ + public final Flux concatMapDelayError(Function> mapper, int prefetch) { + return concatMapDelayError(mapper, true, prefetch); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux}, sequentially and + * preserving order using concatenation. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMap(Function) flatMap} and {@link #flatMapSequential(Function) flatMapSequential}: + *

    + *
  • Generation of inners and subscription: this operator waits for one + * inner to complete before generating the next one and subscribing to it.
  • + *
  • Ordering of the flattened values: this operator naturally preserves + * the same order as the source elements, concatenating the inners from each source + * element sequentially.
  • + *
  • Interleaving: this operator does not let values from different inners + * interleave (concatenation).
  • + *
+ * + *

+ * 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. + * + *

+ * + * + *

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 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 the produced concatenated type + * + * @return a concatenated {@link Flux} + * + */ + 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)); + } + return onAssembly(new FluxConcatMap<>(this, mapper, Queues.get(prefetch), prefetch, errorMode)); + } + + /** + * Transform the items emitted by this {@link Flux} into {@link Iterable}, then flatten the elements from those by + * concatenating them into a single {@link Flux}. For each iterable, {@link Iterable#iterator()} will be called + * at least once and at most twice. + * + *

+ * + *

+ * This operator inspects each {@link Iterable}'s {@link Spliterator} to assess if the iteration + * can be guaranteed to be finite (see {@link Operators#onDiscardMultiple(Iterator, boolean, Context)}). + * Since the default Spliterator wraps the Iterator we can have two {@link Iterable#iterator()} + * calls per iterable. This second invocation is skipped on a {@link Collection} however, a type which is + * assumed to be always finite. + *

+ * Note that unlike {@link #flatMap(Function)} and {@link #concatMap(Function)}, with Iterable there is + * no notion of eager vs lazy inner subscription. The content of the Iterables are all played sequentially. + * Thus {@code flatMapIterable} and {@code concatMapIterable} are equivalent offered as a discoverability + * improvement for users that explore the API with the concat vs flatMap expectation. + * + *

Discard Support: Upon cancellation, this operator discards {@code T} elements it prefetched and, in + * some cases, attempts to discard remainder of the currently processed {@link Iterable} (if it can + * safely ensure the iterator is finite). Note that this means each {@link Iterable}'s {@link Iterable#iterator()} + * method could be invoked twice. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * (including when fusion is enabled). Exceptions thrown by the consumer are passed to + * the {@link #onErrorContinue(BiConsumer)} error consumer (the value consumer + * is not invoked, as the source element will be part of the sequence). The onNext + * signal is then propagated as normal. + * + * @param mapper the {@link Function} to transform input sequence into N {@link Iterable} + * @param the merged output sequence type + * + * @return a concatenation of the values from the Iterables obtained from each element in this {@link Flux} + */ + public final Flux concatMapIterable(Function> mapper) { + return concatMapIterable(mapper, Queues.XS_BUFFER_SIZE); + } + + /** + * Transform the items emitted by this {@link Flux} into {@link Iterable}, then flatten the emissions from those by + * concatenating them into a single {@link Flux}. + * The prefetch argument allows to give an arbitrary prefetch size to the upstream source. + * For each iterable, {@link Iterable#iterator()} will be called at least once and at most twice. + * + *

+ * + *

+ * This operator inspects each {@link Iterable}'s {@link Spliterator} to assess if the iteration + * can be guaranteed to be finite (see {@link Operators#onDiscardMultiple(Iterator, boolean, Context)}). + * Since the default Spliterator wraps the Iterator we can have two {@link Iterable#iterator()} + * calls per iterable. This second invocation is skipped on a {@link Collection} however, a type which is + * assumed to be always finite. + *

+ * Note that unlike {@link #flatMap(Function)} and {@link #concatMap(Function)}, with Iterable there is + * no notion of eager vs lazy inner subscription. The content of the Iterables are all played sequentially. + * Thus {@code flatMapIterable} and {@code concatMapIterable} are equivalent offered as a discoverability + * improvement for users that explore the API with the concat vs flatMap expectation. + * + *

Discard Support: Upon cancellation, this operator discards {@code T} elements it prefetched and, in + * some cases, attempts to discard remainder of the currently processed {@link Iterable} (if it can + * safely ensure the iterator is finite). Note that this means each {@link Iterable}'s {@link Iterable#iterator()} + * method could be invoked twice. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * (including when fusion is enabled). Exceptions thrown by the consumer are passed to + * the {@link #onErrorContinue(BiConsumer)} error consumer (the value consumer + * is not invoked, as the source element will be part of the sequence). The onNext + * signal is then propagated as normal. + * + * @param mapper the {@link Function} to transform input sequence into N {@link Iterable} + * @param prefetch the number of values to request from the source upon subscription, to be transformed to {@link Iterable} + * @param the merged output sequence type + * + * @return a concatenation of the values from the Iterables obtained from each element in this {@link Flux} + */ + public final Flux concatMapIterable(Function> mapper, + int prefetch) { + return onAssembly(new FluxFlattenIterable<>(this, mapper, prefetch, + Queues.get(prefetch))); + } + + /** + * Concatenate emissions of this {@link Flux} with the provided {@link Publisher} (no interleave). + *

+ * + * + * @param other the {@link Publisher} sequence to concat after this {@link Flux} + * + * @return a concatenated {@link Flux} + */ + public final Flux concatWith(Publisher other) { + if (this instanceof FluxConcatArray) { + @SuppressWarnings({ "unchecked" }) + FluxConcatArray fluxConcatArray = (FluxConcatArray) this; + + return fluxConcatArray.concatAdditionalSourceLast(other); + } + return concat(this, other); + } + + /** + * 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. + *

+ * A {@link Context} (and its {@link ContextView}) is tied to a given subscription + * and is read by querying the downstream {@link Subscriber}. {@link Subscriber} that + * don't enrich the context instead access their own downstream's context. As a result, + * this operator conceptually 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 contextToAppend the {@link ContextView} to merge with the downstream {@link Context}, + * resulting in a new more complete {@link Context} that will be visible from upstream. + * + * @return a contextualized {@link Flux} + * @see ContextView + */ + public final Flux contextWrite(ContextView contextToAppend) { + return contextWrite(c -> c.putAll(contextToAppend)); + } + + /** + * Enrich the {@link Context} visible from downstream for the benefit of upstream + * operators, by applying a {@link Function} to the downstream {@link Context}. + *

+ * The {@link Function} takes a {@link Context} for convenience, allowing to easily + * call {@link Context#put(Object, Object) write APIs} to return a new {@link Context}. + *

+ * A {@link Context} (and its {@link ContextView}) is tied to a given subscription + * and is read by querying the downstream {@link Subscriber}. {@link Subscriber} that + * don't enrich the context instead access their own downstream's context. As a result, + * this operator conceptually 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 contextModifier the {@link Function} to apply to the downstream {@link Context}, + * resulting in a new more complete {@link Context} that will be visible from upstream. + * + * @return a contextualized {@link Flux} + * @see Context + */ + public final Flux contextWrite(Function contextModifier) { + return onAssembly(new FluxContextWrite<>(this, contextModifier)); + } + + /** + * Counts the number of values in this {@link Flux}. + * The count will be emitted when onComplete is observed. + * + *

+ * + * + * @return a new {@link Mono} of {@link Long} count + */ + public final Mono count() { + return Mono.onAssembly(new MonoCount<>(this)); + } + + /** + * Provide a default unique value if this sequence is completed without any data + *

+ * + * + * @param defaultV the alternate value if this sequence is empty + * + * @return a new {@link Flux} + */ + public final Flux defaultIfEmpty(T defaultV) { + return onAssembly(new FluxDefaultIfEmpty<>(this, defaultV)); + } + + /** + * Delay each of this {@link Flux} elements ({@link Subscriber#onNext} signals) + * by a given {@link Duration}. Signals are delayed and continue on the + * {@link Schedulers#parallel() parallel} default Scheduler, but empty sequences or + * immediate error signals are not delayed. + * + *

+ * + * + * @param delay duration by which to delay each {@link Subscriber#onNext} signal + * @return a delayed {@link Flux} + * @see #delaySubscription(Duration) delaySubscription to introduce a delay at the beginning of the sequence only + */ + public final Flux delayElements(Duration delay) { + return delayElements(delay, Schedulers.parallel()); + } + + /** + * 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 + * {@link Scheduler}, but empty sequences or immediate error signals are not delayed. + * + *

+ * + * + * @param delay period to delay each {@link Subscriber#onNext} signal + * @param timer a time-capable {@link Scheduler} instance to delay each signal on + * @return a delayed {@link Flux} + */ + public final Flux delayElements(Duration delay, Scheduler timer) { + return delayUntil(d -> Mono.delay(delay, timer)); + } + + /** + * Shift this {@link Flux} forward in time by a given {@link Duration}. + * Unlike with {@link #delayElements(Duration)}, elements are shifted forward in time + * 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 the {@link Schedulers#parallel() parallel} + * {@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} + * of 1s will still emit at 10Hz, with an initial "hiccup" of 1s. + * On the other hand, {@link #delayElements(Duration)} would end up emitting + * at 1Hz. + *

+ * This is closer to {@link #delaySubscription(Duration)}, except the source + * is subscribed to immediately. + * + *

+ * + * + *

Discard Support: This operator discards elements currently being delayed + * * if the sequence is cancelled during the delay. + * + * @param delay {@link Duration} to shift the sequence by + * + * @return an shifted {@link Flux} emitting at the same frequency as the source + */ + public final Flux delaySequence(Duration delay) { + return delaySequence(delay, Schedulers.parallel()); + } + + /** + * Shift this {@link Flux} forward in time by a given {@link Duration}. + * Unlike with {@link #delayElements(Duration, Scheduler)}, elements are shifted forward in time + * 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 + * sequences or immediate error signals are not delayed. + *

+ * With this operator, a source emitting at 10Hz with a delaySequence {@link Duration} + * of 1s will still emit at 10Hz, with an initial "hiccup" of 1s. + * On the other hand, {@link #delayElements(Duration, Scheduler)} would end up emitting + * at 1Hz. + *

+ * This is closer to {@link #delaySubscription(Duration, Scheduler)}, except the source + * is subscribed to immediately. + * + *

+ * + * + *

Discard Support: This operator discards elements currently being delayed + * if the sequence is cancelled during the delay. + * + * @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 + */ + public final Flux delaySequence(Duration delay, Scheduler timer) { + return onAssembly(new FluxDelaySequence<>(this, delay, timer)); + } + + /** + * Subscribe to this {@link Flux} and generate a {@link Publisher} from each of this + * Flux elements, each acting as a trigger for relaying said element. + *

+ * That is to say, the resulting {@link Flux} delays each of its emission until the + * associated trigger Publisher terminates. + *

+ * In case of an error either in the source or in a trigger, that error is propagated + * immediately downstream. + * Note that unlike with the {@link Mono#delayUntil(Function) Mono variant} there is + * no fusion of subsequent calls. + *

+ * + * + * @param triggerProvider a {@link Function} that maps each element into a + * {@link Publisher} whose termination will trigger relaying the value. + * + * @return this Flux, but with elements delayed until their derived publisher terminates. + */ + public final Flux delayUntil(Function> triggerProvider) { + return concatMap(v -> Mono.just(v) + .delayUntil(triggerProvider)); + } + + /** + * Delay the {@link Flux#subscribe(Subscriber) subscription} to this {@link Flux} source until the given + * period elapses. The delay is introduced through the {@link Schedulers#parallel() parallel} default Scheduler. + * + *

+ * + * + * @param delay duration before subscribing this {@link Flux} + * + * @return a delayed {@link Flux} + * + */ + public final Flux delaySubscription(Duration delay) { + return delaySubscription(delay, Schedulers.parallel()); + } + + /** + * Delay the {@link Flux#subscribe(Subscriber) subscription} to this {@link Flux} source until the given + * period elapses, as measured on the user-provided {@link Scheduler}. + * + *

+ * + * + * @param delay {@link Duration} before subscribing this {@link Flux} + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a delayed {@link Flux} + */ + public final Flux delaySubscription(Duration delay, Scheduler timer) { + return delaySubscription(Mono.delay(delay, timer)); + } + + /** + * Delay the {@link Flux#subscribe(Subscriber) subscription} to this {@link Flux} + * source until another {@link Publisher} signals a value or completes. + * + *

+ * + * + * @param subscriptionDelay a companion {@link Publisher} whose onNext/onComplete signal will trigger the {@link Flux#subscribe(Subscriber) subscription} + * @param the other source type + * + * @return a delayed {@link Flux} + * + */ + public final Flux delaySubscription(Publisher subscriptionDelay) { + return onAssembly(new FluxDelaySubscription<>(this, subscriptionDelay)); + } + + /** + * An operator working only if this {@link Flux} emits onNext, onError or onComplete {@link Signal} + * instances, transforming these {@link #materialize() materialized} signals into + * real signals on the {@link Subscriber}. + * The error {@link Signal} will trigger onError and complete {@link Signal} will trigger + * onComplete. + * + *

+ * + * + * @param the dematerialized type + * + * @return a dematerialized {@link Flux} + * @see #materialize() + */ + public final Flux dematerialize() { + @SuppressWarnings("unchecked") + Flux> thiz = (Flux>) this; + return onAssembly(new FluxDematerialize<>(thiz)); + } + + /** + * For each {@link Subscriber}, track elements from this {@link Flux} that have been + * seen and filter out duplicates. + *

+ * The values themselves are recorded into a {@link HashSet} for distinct detection. + * Use {@code distinct(Object::hashcode)} if you want a more lightweight approach that + * doesn't retain all the objects, but is more susceptible to falsely considering two + * elements as distinct due to a hashcode collision. + * + *

+ * + * + *

Discard Support: This operator discards elements that don't match the distinct predicate, + * but you should use the version with a cleanup if you need discarding of keys + * categorized by the operator as "seen". See {@link #distinct(Function, Supplier, BiPredicate, Consumer)}. + * + * @return a filtering {@link Flux} only emitting distinct values + */ + public final Flux distinct() { + return distinct(identityFunction()); + } + + /** + * For each {@link Subscriber}, track elements from this {@link Flux} that have been + * seen and filter out duplicates, as compared by a key extracted through the user + * provided {@link Function}. + * + *

+ * + * + *

Discard Support: This operator discards elements that don't match the distinct predicate, + * but you should use the version with a cleanup if you need discarding of keys + * categorized by the operator as "seen". See {@link #distinct(Function, Supplier, BiPredicate, Consumer)}. + * + * @param keySelector function to compute comparison key for each element + * @param the type of the key extracted from each value in this sequence + * + * @return a filtering {@link Flux} only emitting values with distinct keys + */ + public final Flux distinct(Function keySelector) { + return distinct(keySelector, hashSetSupplier()); + } + + /** + * For each {@link Subscriber}, track elements from this {@link Flux} that have been + * seen and filter out duplicates, as compared by a key extracted through the user + * provided {@link Function} and by the {@link Collection#add(Object) add method} + * of the {@link Collection} supplied (typically a {@link Set}). + * + *

+ * + * + *

Discard Support: This operator discards elements that don't match the distinct predicate, + * but you should use the version with a cleanup if you need discarding of keys + * categorized by the operator as "seen". See {@link #distinct(Function, Supplier, BiPredicate, Consumer)}. + * + * @param keySelector function to compute comparison key for each element + * @param distinctCollectionSupplier supplier of the {@link Collection} used for distinct + * check through {@link Collection#add(Object) add} of the key. + * + * @param the type of the key extracted from each value in this sequence + * @param the type of Collection used for distinct checking of keys + * + * @return a filtering {@link Flux} only emitting values with distinct keys + */ + public final > Flux distinct( + Function keySelector, + Supplier distinctCollectionSupplier) { + return this.distinct(keySelector, distinctCollectionSupplier, Collection::add, Collection::clear); + } + + /** + * For each {@link Subscriber}, track elements from this {@link Flux} that have been + * seen and filter out duplicates, as compared by applying a {@link BiPredicate} on + * an arbitrary user-supplied {@code } store and a key extracted through the user + * provided {@link Function}. The BiPredicate should typically add the key to the + * arbitrary store for further comparison. A cleanup callback is also invoked on the + * store upon termination of the sequence. + * + *

+ * + * + *

Discard Support: This operator discards elements that don't match the distinct predicate, + * but you should use the {@code cleanup} as well if you need discarding of keys + * categorized by the operator as "seen". + * + * @param keySelector function to compute comparison key for each element + * @param distinctStoreSupplier supplier of the arbitrary store object used in distinct + * checks along the extracted key. + * @param distinctPredicate the {@link BiPredicate} to apply to the arbitrary store + + * extracted key to perform a distinct check. Since nothing is assumed of the store, + * this predicate should also add the key to the store as necessary. + * @param cleanup the cleanup callback to invoke on the store upon termination. + * + * @param the type of the key extracted from each value in this sequence + * @param the type of store backing the {@link BiPredicate} + * + * @return a filtering {@link Flux} only emitting values with distinct keys + */ + public final Flux distinct( + Function keySelector, + Supplier distinctStoreSupplier, + BiPredicate distinctPredicate, + Consumer cleanup) { + if (this instanceof Fuseable) { + return onAssembly(new FluxDistinctFuseable<>(this, keySelector, + distinctStoreSupplier, distinctPredicate, cleanup)); + } + return onAssembly(new FluxDistinct<>(this, keySelector, distinctStoreSupplier, distinctPredicate, cleanup)); + } + + /** + * Filter out subsequent repetitions of an element (that is, if they arrive right after + * one another). + * + *

+ * + *

+ * The last distinct value seen is retained for further comparison, which is done + * on the values themselves using {@link Object#equals(Object) the equals method}. + * Use {@code distinctUntilChanged(Object::hashcode)} if you want a more lightweight approach that + * doesn't retain all the objects, but is more susceptible to falsely considering two + * elements as distinct due to a hashcode collision. + * + *

Discard Support: Although this operator discards elements that are considered as "already seen", + * it is not recommended for cases where discarding is needed as the operator doesn't + * discard the "key" (in this context, the distinct instance that was last seen). + * + * @return a filtering {@link Flux} with only one occurrence in a row of each element + * (yet elements can repeat in the overall sequence) + */ + public final Flux distinctUntilChanged() { + return distinctUntilChanged(identityFunction()); + } + + /** + * Filter out subsequent repetitions of an element (that is, if they arrive right after + * one another), as compared by a key extracted through the user provided {@link Function} + * using equality. + * + *

+ * + * + *

Discard Support: This operator discards elements that are considered as "already seen". + * The keys themselves are not discarded. + * + * @param keySelector function to compute comparison key for each element + * @param the type of the key extracted from each value in this sequence + * + * @return a filtering {@link Flux} with only one occurrence in a row of each element of + * the same key (yet element keys can repeat in the overall sequence) + */ + public final Flux distinctUntilChanged(Function keySelector) { + return distinctUntilChanged(keySelector, equalPredicate()); + } + + /** + * Filter out subsequent repetitions of an element (that is, if they arrive right + * after one another), as compared by a key extracted through the user provided {@link + * Function} and then comparing keys with the supplied {@link BiPredicate}. + *

+ * + * + *

Discard Support: This operator discards elements that are considered as "already seen" + * (for which the {@code keyComparator} returns {@literal true}). The keys themselves + * are not discarded. + * + * @param keySelector function to compute comparison key for each element + * @param keyComparator predicate used to compare keys. + * @param the type of the key extracted from each value in this sequence + * + * @return a filtering {@link Flux} with only one occurrence in a row of each element + * of the same key for which the predicate returns true (yet element keys can repeat + * in the overall sequence) + */ + public final Flux distinctUntilChanged(Function keySelector, + BiPredicate keyComparator) { + return onAssembly(new FluxDistinctUntilChanged<>(this, + keySelector, + keyComparator)); + } + + /** + * Add behavior (side-effect) triggered after the {@link Flux} terminates, either by completing downstream successfully or with an error. + *

+ * + *

+ * The relevant signal is propagated downstream, then the {@link Runnable} is executed. + * + * @param afterTerminate the callback to call after {@link Subscriber#onComplete} or {@link Subscriber#onError} + * + * @return an observed {@link Flux} + */ + public final Flux doAfterTerminate(Runnable afterTerminate) { + Objects.requireNonNull(afterTerminate, "afterTerminate"); + return doOnSignal(this, null, null, null, null, afterTerminate, null, null); + } + + /** + * Add behavior (side-effect) triggered when the {@link Flux} is cancelled. + *

+ * + *

+ * The handler is executed first, then the cancel signal is propagated upstream + * to the source. + * + * @param onCancel the callback to call on {@link Subscription#cancel} + * + * @return an observed {@link Flux} + */ + public final Flux doOnCancel(Runnable onCancel) { + Objects.requireNonNull(onCancel, "onCancel"); + return doOnSignal(this, null, null, null, null, null, null, onCancel); + } + + /** + * Add behavior (side-effect) triggered when the {@link Flux} completes successfully. + *

+ * + *

+ * The {@link Runnable} is executed first, then the onComplete signal is propagated + * downstream. + * + * @param onComplete the callback to call on {@link Subscriber#onComplete} + * + * @return an observed {@link Flux} + */ + public final Flux doOnComplete(Runnable onComplete) { + Objects.requireNonNull(onComplete, "onComplete"); + return doOnSignal(this, null, null, null, onComplete, null, null, null); + } + + /** + * Potentially modify the behavior of the whole chain of operators upstream of this one to + * conditionally clean up elements that get discarded by these operators. + *

+ * The {@code discardHook} MUST be idempotent and safe to use on any instance of the desired + * type. + * Calls to this method are additive, and the order of invocation of the {@code discardHook} + * is the same as the order of declaration (calling {@code .filter(...).doOnDiscard(first).doOnDiscard(second)} + * will let the filter invoke {@code first} then {@code second} handlers). + *

+ * Two main categories of discarding operators exist: + *

    + *
  • filtering operators, dropping some source elements as part of their designed behavior
  • + *
  • operators that prefetch a few elements and keep them around pending a request, but get cancelled/in error
  • + *
+ * WARNING: Not all operators support this instruction. The ones that do are identified in the javadoc by + * the presence of a Discard Support section. + * + * @param type the {@link Class} of elements in the upstream chain of operators that + * this cleanup hook should take into account. + * @param discardHook a {@link Consumer} of elements in the upstream chain of operators + * that performs the cleanup. + * @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)); + } + + /** + * Add behavior (side-effects) triggered when the {@link Flux} emits an item, fails with an error + * or completes successfully. All these events are represented as a {@link Signal} + * that is passed to the side-effect callback. Note that this is an advanced operator, + * typically used for monitoring of a Flux. These {@link Signal} have a {@link Context} + * associated to them. + *

+ * + *

+ * The {@link Consumer} is executed first, then the relevant signal is propagated + * downstream. + * + * @param signalConsumer the mandatory callback to call on + * {@link Subscriber#onNext(Object)}, {@link Subscriber#onError(Throwable)} and + * {@link Subscriber#onComplete()} + * @return an observed {@link Flux} + * @see #doOnNext(Consumer) + * @see #doOnError(Consumer) + * @see #doOnComplete(Runnable) + * @see #materialize() + * @see Signal + */ + public final Flux doOnEach(Consumer> signalConsumer) { + if (this instanceof Fuseable) { + return onAssembly(new FluxDoOnEachFuseable<>(this, signalConsumer)); + } + return onAssembly(new FluxDoOnEach<>(this, signalConsumer)); + } + + /** + * Add behavior (side-effect) triggered when the {@link Flux} completes with an error. + *

+ * + *

+ * The {@link Consumer} is executed first, then the onError signal is propagated + * downstream. + * + * @param onError the callback to call on {@link Subscriber#onError} + * + * @return an observed {@link Flux} + */ + public final Flux doOnError(Consumer onError) { + Objects.requireNonNull(onError, "onError"); + return doOnSignal(this, null, null, onError, null, null, null, null); + } + + /** + * Add behavior (side-effect) triggered when the {@link Flux} completes with an error matching the given exception type. + *

+ * + *

+ * The {@link Consumer} is executed first, then the onError signal is propagated + * downstream. + * + * @param exceptionType the type of exceptions to handle + * @param onError the error handler for each error + * @param type of the error to handle + * + * @return an observed {@link Flux} + * + */ + public final Flux doOnError(Class exceptionType, + final Consumer onError) { + Objects.requireNonNull(exceptionType, "type"); + @SuppressWarnings("unchecked") + Consumer handler = (Consumer)onError; + return doOnError(exceptionType::isInstance, (handler)); + } + + /** + * Add behavior (side-effect) triggered when the {@link Flux} completes with an error matching the given exception. + *

+ * + *

+ * The {@link Consumer} is executed first, then the onError signal is propagated + * downstream. + * + * @param predicate the matcher for exceptions to handle + * @param onError the error handler for each error + * + * @return an observed {@link Flux} + * + */ + public final Flux doOnError(Predicate predicate, + final Consumer onError) { + Objects.requireNonNull(predicate, "predicate"); + return doOnError(t -> { + if (predicate.test(t)) { + onError.accept(t); + } + }); + } + + /** + * Add behavior (side-effect) triggered when the {@link Flux} emits an item. + *

+ * + *

+ * The {@link Consumer} is executed first, then the onNext signal is propagated + * downstream. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * (including when fusion is enabled). Exceptions thrown by the consumer are passed to + * the {@link #onErrorContinue(BiConsumer)} error consumer (the value consumer + * is not invoked, as the source element will be part of the sequence). The onNext + * signal is then propagated as normal. + * + * @param onNext the callback to call on {@link Subscriber#onNext} + * + * @return an observed {@link Flux} + */ + public final Flux doOnNext(Consumer onNext) { + Objects.requireNonNull(onNext, "onNext"); + return doOnSignal(this, null, onNext, null, null, null, null, null); + } + + /** + * Add behavior (side-effect) triggering a {@link LongConsumer} when this {@link Flux} + * receives any request. + *

+ * Note that non fatal error raised in the callback will not be propagated and + * will simply trigger {@link Operators#onOperatorError(Throwable, Context)}. + * + *

+ * + *

+ * The {@link LongConsumer} is executed first, then the request signal is propagated + * upstream to the parent. + * + * @param consumer the consumer to invoke on each request + * + * @return an observed {@link Flux} + */ + public final Flux doOnRequest(LongConsumer consumer) { + Objects.requireNonNull(consumer, "consumer"); + return doOnSignal(this, null, null, null, null, null, consumer, null); + } + + /** + * Add behavior (side-effect) triggered when the {@link Flux} is being subscribed, + * that is to say when a {@link Subscription} has been produced by the {@link Publisher} + * and is being passed to the {@link Subscriber#onSubscribe(Subscription)}. + *

+ * This method is not intended for capturing the subscription and calling its methods, + * but for side effects like monitoring. For instance, the correct way to cancel a subscription is + * to call {@link Disposable#dispose()} on the Disposable returned by {@link Flux#subscribe()}. + *

+ * + *

+ * The {@link Consumer} is executed first, then the {@link Subscription} is propagated + * downstream to the next subscriber in the chain that is being established. + * + * @param onSubscribe the callback to call on {@link Subscriber#onSubscribe} + * + * @return an observed {@link Flux} + * @see #doFirst(Runnable) + */ + public final Flux doOnSubscribe(Consumer onSubscribe) { + Objects.requireNonNull(onSubscribe, "onSubscribe"); + return doOnSignal(this, onSubscribe, null, null, null, null, null, null); + } + + /** + * Add behavior (side-effect) triggered when the {@link Flux} terminates, either by + * completing successfully or failing with an error. + *

+ * + *

+ * The {@link Runnable} is executed first, then the onComplete/onError signal is propagated + * downstream. + * + * @param onTerminate the callback to call on {@link Subscriber#onComplete} or {@link Subscriber#onError} + * + * @return an observed {@link Flux} + */ + public final Flux doOnTerminate(Runnable onTerminate) { + Objects.requireNonNull(onTerminate, "onTerminate"); + return doOnSignal(this, + null, + null, + e -> onTerminate.run(), + onTerminate, + null, + null, + null); + } + + /** + * Add behavior (side-effect) triggered before the {@link Flux} is + * subscribed to, which should be the first event after assembly time. + *

+ * + *

+ * Note that when several {@link #doFirst(Runnable)} operators are used anywhere in a + * chain of operators, their order of execution is reversed compared to the declaration + * order (as subscribe signal flows backward, from the ultimate subscriber to the source + * publisher): + *


+	 * Flux.just(1, 2)
+	 *     .doFirst(() -> System.out.println("three"))
+	 *     .doFirst(() -> System.out.println("two"))
+	 *     .doFirst(() -> System.out.println("one"));
+	 * //would print one two three
+	 * 
+	 * 
+ *

+ * In case the {@link Runnable} throws an exception, said exception will be directly + * propagated to the subscribing {@link Subscriber} along with a no-op {@link Subscription}, + * similarly to what {@link #error(Throwable)} does. Otherwise, after the handler has + * executed, the {@link Subscriber} is directly subscribed to the original source + * {@link Flux} ({@code this}). + *

+ * This side-effect method provides stronger first guarantees compared to + * {@link #doOnSubscribe(Consumer)}, which is triggered once the {@link Subscription} + * has been set up and passed to the {@link Subscriber}. + * + * @param onFirst the callback to execute before the {@link Flux} is subscribed to + * @return an observed {@link Flux} + */ + public final Flux doFirst(Runnable onFirst) { + Objects.requireNonNull(onFirst, "onFirst"); + if (this instanceof Fuseable) { + return onAssembly(new FluxDoFirstFuseable<>(this, onFirst)); + } + return onAssembly(new FluxDoFirst<>(this, onFirst)); + } + + /** + * Add behavior (side-effect) triggered after the {@link Flux} terminates for any reason, + * including cancellation. The terminating event ({@link SignalType#ON_COMPLETE}, + * {@link SignalType#ON_ERROR} and {@link SignalType#CANCEL}) is passed to the consumer, + * which is executed after the signal has been passed downstream. + *

+ * Note that the fact that the signal is propagated downstream before the callback is + * executed means that several doFinally in a row will be executed in + * reverse order. If you want to assert the execution of the callback + * please keep in mind that the Flux will complete before it is executed, so its + * effect might not be visible immediately after eg. a {@link #blockLast()}. + *

+ * + * + * @param onFinally the callback to execute after a terminal signal (complete, error + * or cancel) + * @return an observed {@link Flux} + */ + 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>} + * 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. + * + *

+ * + * + * @return a new {@link Flux} that emits a tuple of time elapsed in milliseconds and matching data + * @see #timed() + * @see Timed#elapsed() + */ + public final Flux> elapsed() { + return elapsed(Schedulers.parallel()); + } + + /** + * Map this {@link Flux} into {@link reactor.util.function.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. + *

+ * + * + * @param scheduler a {@link Scheduler} instance to {@link Scheduler#now(TimeUnit) read time from} + * + * @return a new {@link Flux} that emits tuples of time elapsed in milliseconds and matching data + * @see #timed(Scheduler) + * @see Timed#elapsed() + */ + public final Flux> elapsed(Scheduler scheduler) { + Objects.requireNonNull(scheduler, "scheduler"); + return onAssembly(new FluxElapsed<>(this, scheduler)); + } + + /** + * Emit only the element at the given index position or {@link IndexOutOfBoundsException} + * if the sequence is shorter. + * + *

+ * + * + *

Discard Support: This operator discards elements that appear before the requested index. + * + * @param index zero-based index of the only item to emit + * + * @return a {@link Mono} of the item at the specified zero-based index + */ + public final Mono elementAt(int index) { + return Mono.onAssembly(new MonoElementAt<>(this, index)); + } + + /** + * Emit only the element at the given index position or fall back to a + * default value if the sequence is shorter. + * + *

+ * + * + *

Discard Support: This operator discards elements that appear before the requested index. + * + * @param index zero-based index of the only item to emit + * @param defaultValue a default value to emit if the sequence is shorter + * + * @return a {@link Mono} of the item at the specified zero-based index or a default value + */ + public final Mono elementAt(int index, T defaultValue) { + return Mono.onAssembly(new MonoElementAt<>(this, index, defaultValue)); + } + + /** + * Recursively expand elements into a graph and emit all the resulting element, + * in a depth-first traversal order. + *

+ * That is: emit one value from this {@link Flux}, expand it and emit the first value + * at this first level of recursion, and so on... When no more recursion is possible, + * backtrack to the previous level and re-apply the strategy. + *

+ * For example, given the hierarchical structure + *

+	 *  A
+	 *   - AA
+	 *     - aa1
+	 *  B
+	 *   - BB
+	 *     - bb1
+	 * 
+ * + * Expands {@code Flux.just(A, B)} into + *
+	 *  A
+	 *  AA
+	 *  aa1
+	 *  B
+	 *  BB
+	 *  bb1
+	 * 
+ * + * @param expander the {@link Function} applied at each level of recursion to expand + * values into a {@link Publisher}, producing a graph. + * @param capacityHint a capacity hint to prepare the inner queues to accommodate n + * elements per level of recursion. + * + * @return a {@link Flux} expanded depth-first + */ + public final Flux expandDeep(Function> expander, + int capacityHint) { + return onAssembly(new FluxExpand<>(this, expander, false, capacityHint)); + } + + /** + * Recursively expand elements into a graph and emit all the resulting element, + * in a depth-first traversal order. + *

+ * That is: emit one value from this {@link Flux}, expand it and emit the first value + * at this first level of recursion, and so on... When no more recursion is possible, + * backtrack to the previous level and re-apply the strategy. + *

+ * For example, given the hierarchical structure + *

+	 *  A
+	 *   - AA
+	 *     - aa1
+	 *  B
+	 *   - BB
+	 *     - bb1
+	 * 
+ * + * Expands {@code Flux.just(A, B)} into + *
+	 *  A
+	 *  AA
+	 *  aa1
+	 *  B
+	 *  BB
+	 *  bb1
+	 * 
+ * + * @param expander the {@link Function} applied at each level of recursion to expand + * values into a {@link Publisher}, producing a graph. + * + * @return a {@link Flux} expanded depth-first + */ + public final Flux expandDeep(Function> expander) { + return expandDeep(expander, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Recursively expand elements into a graph and emit all the resulting element using + * a breadth-first traversal strategy. + *

+ * 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.. + *

+ * For example, given the hierarchical structure + *

+	 *  A
+	 *   - AA
+	 *     - aa1
+	 *  B
+	 *   - BB
+	 *     - bb1
+	 * 
+ * + * Expands {@code Flux.just(A, B)} into + *
+	 *  A
+	 *  B
+	 *  AA
+	 *  BB
+	 *  aa1
+	 *  bb1
+	 * 
+ * + * @param expander the {@link Function} applied at each level of recursion to expand + * values into a {@link Publisher}, producing a graph. + * @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} + */ + public final Flux expand(Function> expander, + int capacityHint) { + return Flux.onAssembly(new FluxExpand<>(this, expander, true, capacityHint)); + } + + /** + * Recursively expand elements into a graph and emit all the resulting element using + * a breadth-first traversal strategy. + *

+ * 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.. + *

+ * For example, given the hierarchical structure + *

+	 *  A
+	 *   - AA
+	 *     - aa1
+	 *  B
+	 *   - BB
+	 *     - bb1
+	 * 
+ * + * Expands {@code Flux.just(A, B)} into + *
+	 *  A
+	 *  B
+	 *  AA
+	 *  BB
+	 *  aa1
+	 *  bb1
+	 * 
+ * + * @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} + */ + public final Flux expand(Function> expander) { + return expand(expander, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Evaluate each source value against the given {@link Predicate}. If the predicate test succeeds, the value is + * emitted. If the predicate test fails, the value is ignored and a request of 1 is made upstream. + * + *

+ * + * + *

Discard Support: This operator discards elements that do not match the filter. It + * also discards elements internally queued for backpressure upon cancellation or error triggered by a data signal. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * (including when fusion is enabled). Exceptions thrown by the predicate are + * considered as if the predicate returned false: they cause the source value to be + * dropped and a new element ({@code request(1)}) being requested from upstream. + * + * @param p the {@link Predicate} to test values against + * + * @return a new {@link Flux} containing only values that pass the predicate test + */ + public final Flux filter(Predicate p) { + if (this instanceof Fuseable) { + return onAssembly(new FluxFilterFuseable<>(this, p)); + } + return onAssembly(new FluxFilter<>(this, p)); + } + + /** + * Test each value emitted by this {@link Flux} asynchronously using a generated + * {@code Publisher} test. A value is replayed if the first item emitted + * by its corresponding test is {@literal true}. It is dropped if its test is either + * empty or its first emitted value is {@literal false}. + *

+ * Note that only the first value of the test publisher is considered, and unless it + * is a {@link Mono}, test will be cancelled after receiving that first value. Test + * publishers are generated and subscribed to in sequence. + * + *

+ * + * + *

Discard Support: This operator discards elements that do not match the filter. It + * also discards elements internally queued for backpressure upon cancellation or error triggered by a data signal. + * + * @param asyncPredicate the function generating a {@link Publisher} of {@link Boolean} + * for each value, to filter the Flux with + * + * @return a filtered {@link Flux} + */ + public final Flux filterWhen(Function> asyncPredicate) { + return filterWhen(asyncPredicate, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Test each value emitted by this {@link Flux} asynchronously using a generated + * {@code Publisher} test. A value is replayed if the first item emitted + * by its corresponding test is {@literal true}. It is dropped if its test is either + * empty or its first emitted value is {@literal false}. + *

+ * Note that only the first value of the test publisher is considered, and unless it + * is a {@link Mono}, test will be cancelled after receiving that first value. Test + * publishers are generated and subscribed to in sequence. + * + *

+ * + * + *

Discard Support: This operator discards elements that do not match the filter. It + * also discards elements internally queued for backpressure upon cancellation or error triggered by a data signal. + * + * @param asyncPredicate the function generating a {@link Publisher} of {@link Boolean} + * for each value, to filter the Flux with + * @param bufferSize the maximum expected number of values to hold pending a result of + * their respective asynchronous predicates, rounded to the next power of two. This is + * capped depending on the size of the heap and the JVM limits, so be careful with + * large values (although eg. {@literal 65536} should still be fine). Also serves as + * the initial request size for the source. + * + * @return a filtered {@link Flux} + */ + public final Flux filterWhen(Function> asyncPredicate, + int bufferSize) { + return onAssembly(new FluxFilterWhen<>(this, asyncPredicate, bufferSize)); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux} through merging, + * which allow them to interleave. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMapSequential(Function) flatMapSequential} and {@link #concatMap(Function) concatMap}: + *

    + *
  • Generation of inners and subscription: this operator is eagerly + * subscribing to its inners.
  • + *
  • Ordering of the flattened values: this operator does not necessarily preserve + * original ordering, as inner element are flattened as they arrive.
  • + *
  • Interleaving: this operator lets values from different inners interleave + * (similar to merging the inner sequences).
  • + *
+ *

+ * + *

+ * + *

Discard Support: This operator discards elements internally queued for backpressure upon cancellation or error triggered by a data signal. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * in the mapper {@link Function}. Exceptions thrown by the mapper then behave as if + * it had mapped the value to an empty publisher. If the mapper does map to a scalar + * publisher (an optimization in which the value can be resolved immediately without + * subscribing to the publisher, e.g. a {@link Mono#fromCallable(Callable)}) but said + * publisher throws, this can be resumed from in the same manner. + * + * @param mapper the {@link Function} to transform input sequence into N sequences {@link Publisher} + * @param the merged output sequence type + * + * @return a new {@link Flux} + */ + public final Flux flatMap(Function> mapper) { + return flatMap(mapper, Queues.SMALL_BUFFER_SIZE, Queues + .XS_BUFFER_SIZE); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux} through merging, + * which allow them to interleave. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMapSequential(Function) flatMapSequential} and {@link #concatMap(Function) concatMap}: + *

    + *
  • Generation of inners and subscription: this operator is eagerly + * subscribing to its inners.
  • + *
  • Ordering of the flattened values: this operator does not necessarily preserve + * original ordering, as inner element are flattened as they arrive.
  • + *
  • Interleaving: this operator lets values from different inners interleave + * (similar to merging the inner sequences).
  • + *
+ * The concurrency argument allows to control how many {@link Publisher} can be + * subscribed to and merged in parallel. In turn, that argument shows the size of + * the first {@link Subscription#request} to the upstream. + * + *

+ * + * + *

Discard Support: This operator discards elements internally queued for backpressure upon cancellation or error triggered by a data signal. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * in the mapper {@link Function}. Exceptions thrown by the mapper then behave as if + * it had mapped the value to an empty publisher. If the mapper does map to a scalar + * publisher (an optimization in which the value can be resolved immediately without + * subscribing to the publisher, e.g. a {@link Mono#fromCallable(Callable)}) but said + * publisher throws, this can be resumed from in the same manner. + * + * @param mapper the {@link Function} to transform input sequence into N sequences {@link Publisher} + * @param concurrency the maximum number of in-flight inner sequences + * + * @param the merged output sequence type + * + * @return a new {@link Flux} + */ + public final Flux flatMap(Function> mapper, int + concurrency) { + return flatMap(mapper, concurrency, Queues.XS_BUFFER_SIZE); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux} through merging, + * which allow them to interleave. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMapSequential(Function) flatMapSequential} and {@link #concatMap(Function) concatMap}: + *

    + *
  • Generation of inners and subscription: this operator is eagerly + * subscribing to its inners.
  • + *
  • Ordering of the flattened values: this operator does not necessarily preserve + * original ordering, as inner element are flattened as they arrive.
  • + *
  • Interleaving: this operator lets values from different inners interleave + * (similar to merging the inner sequences).
  • + *
+ * The concurrency argument allows to control how many {@link Publisher} can be + * subscribed to and merged in parallel. In turn, that argument shows the size of + * the first {@link Subscription#request} to the upstream. + * The prefetch argument allows to give an arbitrary prefetch size to the merged + * {@link Publisher} (in other words prefetch size means the size of the first + * {@link Subscription#request} to the merged {@link Publisher}). + * + *

+ * + * + *

Discard Support: This operator discards elements internally queued for backpressure upon cancellation or error triggered by a data signal. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * in the mapper {@link Function}. Exceptions thrown by the mapper then behave as if + * it had mapped the value to an empty publisher. If the mapper does map to a scalar + * publisher (an optimization in which the value can be resolved immediately without + * subscribing to the publisher, e.g. a {@link Mono#fromCallable(Callable)}) but said + * publisher throws, this can be resumed from in the same manner. + * + * @param mapper the {@link Function} to transform input sequence into N sequences {@link Publisher} + * @param concurrency the maximum number of in-flight inner sequences + * @param prefetch the maximum in-flight elements from each inner {@link Publisher} sequence + * + * @param the merged output sequence type + * + * @return a merged {@link Flux} + */ + public final Flux flatMap(Function> mapper, int + concurrency, int prefetch) { + return flatMap(mapper, false, concurrency, prefetch); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux} through merging, + * which allow them to interleave. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMapSequential(Function) flatMapSequential} and {@link #concatMap(Function) concatMap}: + *

    + *
  • Generation of inners and subscription: this operator is eagerly + * subscribing to its inners.
  • + *
  • Ordering of the flattened values: this operator does not necessarily preserve + * original ordering, as inner element are flattened as they arrive.
  • + *
  • Interleaving: this operator lets values from different inners interleave + * (similar to merging the inner sequences).
  • + *
+ * The concurrency argument allows to control how many {@link Publisher} can be + * subscribed to and merged in parallel. The prefetch argument allows to give an + * arbitrary prefetch size to the merged {@link Publisher}. This variant will delay + * any error until after the rest of the flatMap backlog has been processed. + * + *

+ * + * + *

Discard Support: This operator discards elements internally queued for backpressure upon cancellation or error triggered by a data signal. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * in the mapper {@link Function}. Exceptions thrown by the mapper then behave as if + * it had mapped the value to an empty publisher. If the mapper does map to a scalar + * publisher (an optimization in which the value can be resolved immediately without + * subscribing to the publisher, e.g. a {@link Mono#fromCallable(Callable)}) but said + * publisher throws, this can be resumed from in the same manner. + * + * @param mapper the {@link Function} to transform input sequence into N sequences {@link Publisher} + * @param concurrency the maximum number of in-flight inner sequences + * @param prefetch the maximum in-flight elements from each inner {@link Publisher} sequence + * + * @param the merged output sequence type + * + * @return a merged {@link Flux} + */ + public final Flux flatMapDelayError(Function> mapper, + int concurrency, int prefetch) { + return flatMap(mapper, true, concurrency, prefetch); + } + + /** + * Transform the signals emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux} through merging, + * which allow them to interleave. Note that at least one of the signal mappers must + * be provided, and all provided mappers must produce a publisher. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMapSequential(Function) flatMapSequential} and {@link #concatMap(Function) concatMap}: + *

    + *
  • Generation of inners and subscription: this operator is eagerly + * subscribing to its inners.
  • + *
  • Ordering of the flattened values: this operator does not necessarily preserve + * original ordering, as inner element are flattened as they arrive.
  • + *
  • Interleaving: this operator lets values from different inners interleave + * (similar to merging the inner sequences).
  • + *
+ *

+ * OnError will be transformed into completion signal after its mapping callback has been applied. + *

+ * + * + * @param mapperOnNext the {@link Function} to call on next data and returning a sequence to merge. + * Use {@literal null} to ignore (provided at least one other mapper is specified). + * @param mapperOnError the {@link Function} to call on error signal and returning a sequence to merge. + * Use {@literal null} to ignore (provided at least one other mapper is specified). + * @param mapperOnComplete the {@link Function} to call on complete signal and returning a sequence to merge. + * Use {@literal null} to ignore (provided at least one other mapper is specified). + * @param the output {@link Publisher} type target + * + * @return a new {@link Flux} + */ + public final Flux flatMap( + @Nullable Function> mapperOnNext, + @Nullable Function> mapperOnError, + @Nullable Supplier> mapperOnComplete) { + return onAssembly(new FluxFlatMap<>( + new FluxMapSignal<>(this, mapperOnNext, mapperOnError, mapperOnComplete), + identityFunction(), + false, Queues.XS_BUFFER_SIZE, + Queues.xs(), Queues.XS_BUFFER_SIZE, + Queues.xs() + )); + } + + /** + * Transform the items emitted by this {@link Flux} into {@link Iterable}, then flatten the elements from those by + * merging them into a single {@link Flux}. For each iterable, {@link Iterable#iterator()} will be called at least + * once and at most twice. + * + *

+ * + *

+ * This operator inspects each {@link Iterable}'s {@link Spliterator} to assess if the iteration + * can be guaranteed to be finite (see {@link Operators#onDiscardMultiple(Iterator, boolean, Context)}). + * Since the default Spliterator wraps the Iterator we can have two {@link Iterable#iterator()} + * calls per iterable. This second invocation is skipped on a {@link Collection} however, a type which is + * assumed to be always finite. + *

+ * Note that unlike {@link #flatMap(Function)} and {@link #concatMap(Function)}, with Iterable there is + * no notion of eager vs lazy inner subscription. The content of the Iterables are all played sequentially. + * Thus {@code flatMapIterable} and {@code concatMapIterable} are equivalent offered as a discoverability + * improvement for users that explore the API with the concat vs flatMap expectation. + * + *

Discard Support: Upon cancellation, this operator discards {@code T} elements it prefetched and, in + * some cases, attempts to discard remainder of the currently processed {@link Iterable} (if it can + * safely ensure the iterator is finite). Note that this means each {@link Iterable}'s {@link Iterable#iterator()} + * method could be invoked twice. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * (including when fusion is enabled). Exceptions thrown by the consumer are passed to + * the {@link #onErrorContinue(BiConsumer)} error consumer (the value consumer + * is not invoked, as the source element will be part of the sequence). The onNext + * signal is then propagated as normal. + * + * @param mapper the {@link Function} to transform input sequence into N {@link Iterable} + * + * @param the merged output sequence type + * + * @return a concatenation of the values from the Iterables obtained from each element in this {@link Flux} + */ + public final Flux flatMapIterable(Function> mapper) { + return flatMapIterable(mapper, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Transform the items emitted by this {@link Flux} into {@link Iterable}, then flatten the emissions from those by + * merging them into a single {@link Flux}. The prefetch argument allows to give an + * arbitrary prefetch size to the upstream source. + * For each iterable, {@link Iterable#iterator()} will be called at least once and at most twice. + * + *

+ * + *

+ * This operator inspects each {@link Iterable}'s {@link Spliterator} to assess if the iteration + * can be guaranteed to be finite (see {@link Operators#onDiscardMultiple(Iterator, boolean, Context)}). + * Since the default Spliterator wraps the Iterator we can have two {@link Iterable#iterator()} + * calls per iterable. This second invocation is skipped on a {@link Collection} however, a type which is + * assumed to be always finite. + *

+ * Note that unlike {@link #flatMap(Function)} and {@link #concatMap(Function)}, with Iterable there is + * no notion of eager vs lazy inner subscription. The content of the Iterables are all played sequentially. + * Thus {@code flatMapIterable} and {@code concatMapIterable} are equivalent offered as a discoverability + * improvement for users that explore the API with the concat vs flatMap expectation. + * + *

Discard Support: Upon cancellation, this operator discards {@code T} elements it prefetched and, in + * some cases, attempts to discard remainder of the currently processed {@link Iterable} (if it can + * safely ensure the iterator is finite). + * Note that this means each {@link Iterable}'s {@link Iterable#iterator()} method could be invoked twice. + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * (including when fusion is enabled). Exceptions thrown by the consumer are passed to + * the {@link #onErrorContinue(BiConsumer)} error consumer (the value consumer + * is not invoked, as the source element will be part of the sequence). The onNext + * signal is then propagated as normal. + * + * @param mapper the {@link Function} to transform input sequence into N {@link Iterable} + * @param prefetch the number of values to request from the source upon subscription, to be transformed to {@link Iterable} + * + * @param the merged output sequence type + * + * @return a concatenation of the values from the Iterables obtained from each element in this {@link Flux} + */ + public final Flux flatMapIterable(Function> mapper, int prefetch) { + return onAssembly(new FluxFlattenIterable<>(this, mapper, prefetch, + Queues.get(prefetch))); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux}, but merge them in + * the order of their source element. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMap(Function) flatMap} and {@link #concatMap(Function) concatMap}: + *

    + *
  • Generation of inners and subscription: this operator is eagerly + * subscribing to its inners (like flatMap).
  • + *
  • Ordering of the flattened values: this operator queues elements from + * late inners until all elements from earlier inners have been emitted, thus emitting + * inner sequences as a whole, in an order that matches their source's order.
  • + *
  • Interleaving: this operator does not let values from different inners + * interleave (similar looking result to concatMap, but due to queueing of values + * that would have been interleaved otherwise).
  • + *
+ * + *

+ * That is to say, whenever a source element is emitted it is transformed to an inner + * {@link Publisher}. However, if such an early inner takes more time to complete than + * subsequent faster inners, the data from these faster inners will be queued until + * the earlier inner completes, so as to maintain source ordering. + * + *

+ * + * + * @param mapper the {@link Function} to transform input sequence into N sequences {@link Publisher} + * @param the merged output sequence type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public final Flux flatMapSequential(Function> mapper) { + return flatMapSequential(mapper, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux}, but merge them in + * the order of their source element. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMap(Function) flatMap} and {@link #concatMap(Function) concatMap}: + *

    + *
  • Generation of inners and subscription: this operator is eagerly + * subscribing to its inners (like flatMap).
  • + *
  • Ordering of the flattened values: this operator queues elements from + * late inners until all elements from earlier inners have been emitted, thus emitting + * inner sequences as a whole, in an order that matches their source's order.
  • + *
  • Interleaving: this operator does not let values from different inners + * interleave (similar looking result to concatMap, but due to queueing of values + * that would have been interleaved otherwise).
  • + *
+ * + *

+ * That is to say, whenever a source element is emitted it is transformed to an inner + * {@link Publisher}. However, if such an early inner takes more time to complete than + * subsequent faster inners, the data from these faster inners will be queued until + * the earlier inner completes, so as to maintain source ordering. + * + *

+ * The concurrency argument allows to control how many merged {@link Publisher} can happen in parallel. + * + *

+ * + * + * @param mapper the {@link Function} to transform input sequence into N sequences {@link Publisher} + * @param maxConcurrency the maximum number of in-flight inner sequences + * @param the merged output sequence type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public final Flux flatMapSequential(Function> mapper, int maxConcurrency) { + return flatMapSequential(mapper, maxConcurrency, Queues.XS_BUFFER_SIZE); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux}, but merge them in + * the order of their source element. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMap(Function) flatMap} and {@link #concatMap(Function) concatMap}: + *

    + *
  • Generation of inners and subscription: this operator is eagerly + * subscribing to its inners (like flatMap).
  • + *
  • Ordering of the flattened values: this operator queues elements from + * late inners until all elements from earlier inners have been emitted, thus emitting + * inner sequences as a whole, in an order that matches their source's order.
  • + *
  • Interleaving: this operator does not let values from different inners + * interleave (similar looking result to concatMap, but due to queueing of values + * that would have been interleaved otherwise).
  • + *
+ * + *

+ * That is to say, whenever a source element is emitted it is transformed to an inner + * {@link Publisher}. However, if such an early inner takes more time to complete than + * subsequent faster inners, the data from these faster inners will be queued until + * the earlier inner completes, so as to maintain source ordering. + * + *

+ * The concurrency argument allows to control how many merged {@link Publisher} + * can happen in parallel. The prefetch argument allows to give an arbitrary prefetch + * size to the merged {@link Publisher}. + * + *

+ * + * + * @param mapper the {@link Function} to transform input sequence into N sequences {@link Publisher} + * @param maxConcurrency the maximum number of in-flight inner sequences + * @param prefetch the maximum in-flight elements from each inner {@link Publisher} sequence + * @param the merged output sequence type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public final Flux flatMapSequential(Function> mapper, int maxConcurrency, int prefetch) { + return flatMapSequential(mapper, false, maxConcurrency, prefetch); + } + + /** + * Transform the elements emitted by this {@link Flux} asynchronously into Publishers, + * then flatten these inner publishers into a single {@link Flux}, but merge them in + * the order of their source element. + *

+ * There are three dimensions to this operator that can be compared with + * {@link #flatMap(Function) flatMap} and {@link #concatMap(Function) concatMap}: + *

    + *
  • Generation of inners and subscription: this operator is eagerly + * subscribing to its inners (like flatMap).
  • + *
  • Ordering of the flattened values: this operator queues elements from + * late inners until all elements from earlier inners have been emitted, thus emitting + * inner sequences as a whole, in an order that matches their source's order.
  • + *
  • Interleaving: this operator does not let values from different inners + * interleave (similar looking result to concatMap, but due to queueing of values + * that would have been interleaved otherwise).
  • + *
+ * + *

+ * That is to say, whenever a source element is emitted it is transformed to an inner + * {@link Publisher}. However, if such an early inner takes more time to complete than + * subsequent faster inners, the data from these faster inners will be queued until + * the earlier inner completes, so as to maintain source ordering. + * + *

+ * The concurrency argument allows to control how many merged {@link Publisher} + * can happen in parallel. The prefetch argument allows to give an arbitrary prefetch + * size to the merged {@link Publisher}. This variant will delay any error until after the + * rest of the flatMap backlog has been processed. + * + *

+ * + * + * @param mapper the {@link Function} to transform input sequence into N sequences {@link Publisher} + * @param maxConcurrency the maximum number of in-flight inner sequences + * @param prefetch the maximum in-flight elements from each inner {@link Publisher} sequence + * @param the merged output sequence type + * + * @return a merged {@link Flux}, subscribing early but keeping the original ordering + */ + public final Flux flatMapSequentialDelayError(Function> mapper, int maxConcurrency, int prefetch) { + return flatMapSequential(mapper, true, maxConcurrency, prefetch); + } + + /** + * The prefetch configuration of the {@link Flux} + * @return the prefetch configuration of the {@link Flux}, -1 if unspecified + */ + public int getPrefetch() { + return -1; + } + + /** + * Divide this sequence into dynamically created {@link Flux} (or groups) for each + * unique key, as produced by the provided keyMapper {@link Function}. Note that + * groupBy works best with a low cardinality of groups, so chose your keyMapper + * function accordingly. + * + *

+ * + * + *

+ * The groups need to be drained and consumed downstream for groupBy to work correctly. + * Notably when the criteria produces a large amount of groups, it can lead to hanging + * if the groups are not suitably consumed downstream (eg. due to a {@code flatMap} + * with a {@code maxConcurrency} parameter that is set too low). + * + *

+ * Note that groups 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 specific group more than once: groups are unicast. + * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, + * as these operators are based on re-subscription. + * + * @param keyMapper the key mapping {@link Function} that evaluates an incoming data and returns a key. + * @param the key type extracted from each value of this sequence + * + * @return a {@link Flux} of {@link GroupedFlux} grouped sequences + */ + public final Flux> groupBy(Function keyMapper) { + return groupBy(keyMapper, identityFunction()); + } + + /** + * Divide this sequence into dynamically created {@link Flux} (or groups) for each + * unique key, as produced by the provided keyMapper {@link Function}. Note that + * groupBy works best with a low cardinality of groups, so chose your keyMapper + * function accordingly. + * + *

+ * + * + *

+ * The groups need to be drained and consumed downstream for groupBy to work correctly. + * Notably when the criteria produces a large amount of groups, it can lead to hanging + * if the groups are not suitably consumed downstream (eg. due to a {@code flatMap} + * with a {@code maxConcurrency} parameter that is set too low). + * + *

+ * Note that groups 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 specific group more than once: groups are unicast. + * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, + * as these operators are based on re-subscription. + * + * @param keyMapper the key mapping {@link Function} that evaluates an incoming data and returns a key. + * @param prefetch the number of values to prefetch from the source + * @param the key type extracted from each value of this sequence + * + * @return a {@link Flux} of {@link GroupedFlux} grouped sequences + */ + public final Flux> groupBy(Function keyMapper, int prefetch) { + return groupBy(keyMapper, identityFunction(), prefetch); + } + + /** + * Divide this sequence into dynamically created {@link Flux} (or groups) for each + * unique key, as produced by the provided keyMapper {@link Function}. Source elements + * are also mapped to a different value using the {@code valueMapper}. Note that + * groupBy works best with a low cardinality of groups, so chose your keyMapper + * function accordingly. + * + *

+ * + * + *

+ * The groups need to be drained and consumed downstream for groupBy to work correctly. + * Notably when the criteria produces a large amount of groups, it can lead to hanging + * if the groups are not suitably consumed downstream (eg. due to a {@code flatMap} + * with a {@code maxConcurrency} parameter that is set too low). + * + *

+ * Note that groups 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 specific group more than once: groups are unicast. + * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, + * as these operators are based on re-subscription. + * + * @param keyMapper the key mapping function that evaluates an incoming data and returns a key. + * @param valueMapper the value mapping function that evaluates which data to extract for re-routing. + * @param the key type extracted from each value of this sequence + * @param the value type extracted from each value of this sequence + * + * @return a {@link Flux} of {@link GroupedFlux} grouped sequences + * + */ + public final Flux> groupBy(Function keyMapper, + Function valueMapper) { + return groupBy(keyMapper, valueMapper, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Divide this sequence into dynamically created {@link Flux} (or groups) for each + * unique key, as produced by the provided keyMapper {@link Function}. Source elements + * are also mapped to a different value using the {@code valueMapper}. Note that + * groupBy works best with a low cardinality of groups, so chose your keyMapper + * function accordingly. + * + *

+ * + * + *

+ * The groups need to be drained and consumed downstream for groupBy to work correctly. + * Notably when the criteria produces a large amount of groups, it can lead to hanging + * if the groups are not suitably consumed downstream (eg. due to a {@code flatMap} + * with a {@code maxConcurrency} parameter that is set too low). + * + *

+ * Note that groups 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 specific group more than once: groups are unicast. + * This is most noticeable when trying to {@link #retry()} or {@link #repeat()} a window, + * as these operators are based on re-subscription. + * + * @param keyMapper the key mapping function that evaluates an incoming data and returns a key. + * @param valueMapper the value mapping function that evaluates which data to extract for re-routing. + * @param prefetch the number of values to prefetch from the source + * + * @param the key type extracted from each value of this sequence + * @param the value type extracted from each value of this sequence + * + * @return a {@link Flux} of {@link GroupedFlux} grouped sequences + * + */ + public final Flux> groupBy(Function keyMapper, + Function valueMapper, int prefetch) { + return onAssembly(new FluxGroupBy<>(this, keyMapper, valueMapper, + Queues.unbounded(prefetch), + Queues.unbounded(prefetch), prefetch)); + } + + /** + * Map values from two Publishers into time windows and emit combination of values + * in case their windows overlap. The emitted elements are obtained by passing the + * value from this {@link Flux} and a {@link Flux} emitting the value from the other + * {@link Publisher} to a {@link BiFunction}. + *

+ * There are no guarantees in what order the items get combined when multiple items from + * one or both source Publishers overlap. + *

+ * Unlike {@link Flux#join}, items from the second {@link Publisher} will be provided + * as a {@link Flux} to the {@code resultSelector}. + * + *

+ * + * + * @param other the other {@link Publisher} to correlate items with + * @param leftEnd a function that returns a Publisher whose emissions indicate the + * time window for the source value to be considered + * @param rightEnd a function that returns a Publisher whose emissions indicate the + * time window for the {@code right} Publisher value to be considered + * @param resultSelector a function that takes an item emitted by this {@link Flux} and + * a {@link Flux} representation of the overlapping item from the other {@link Publisher} + * and returns the value to be emitted by the resulting {@link Flux} + * @param the type of the elements from the right {@link Publisher} + * @param the type for this {@link Flux} window signals + * @param the type for the right {@link Publisher} window signals + * @param the combined result type + * + * @return a joining {@link Flux} + * @see #join(Publisher, Function, Function, BiFunction) + */ + public final Flux groupJoin( + Publisher other, + Function> leftEnd, + Function> rightEnd, + BiFunction, ? extends R> resultSelector + ) { + return onAssembly(new FluxGroupJoin( + this, other, leftEnd, rightEnd, resultSelector, + Queues.unbounded(Queues.XS_BUFFER_SIZE), + Queues.unbounded(Queues.XS_BUFFER_SIZE))); + } + + /** + * Handle the items emitted by this {@link Flux} by calling a biconsumer with the + * 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()}. + * + *

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)}. + * + * @param handler the handling {@link BiConsumer} + * + * @param the transformed type + * + * @return a transformed {@link Flux} + */ + public final Flux handle(BiConsumer> handler) { + if (this instanceof Fuseable) { + return onAssembly(new FluxHandleFuseable<>(this, handler)); + } + return onAssembly(new FluxHandle<>(this, handler)); + } + + /** + * Emit a single boolean true if any of the elements of this {@link Flux} sequence is + * equal to the provided value. + *

+ * The implementation uses short-circuit logic and completes with true if + * an element matches the value. + * + *

+ * + * + * @param value constant compared to incoming signals + * + * @return a new {@link Flux} with true if any element is equal to a given value and false + * otherwise + * + */ + public final Mono hasElement(T value) { + Objects.requireNonNull(value, "value"); + return any(t -> Objects.equals(value, t)); + } + + /** + * Emit a single boolean true if this {@link Flux} sequence has at least one element. + *

+ * The implementation uses short-circuit logic and completes with true on onNext. + * + *

+ * + * + * @return a new {@link Mono} with true if any value is emitted and false + * otherwise + */ + public final Mono hasElements() { + return Mono.onAssembly(new MonoHasElements<>(this)); + } + + /** + * Hides the identities of this {@link Flux} instance. + *

The main purpose of this operator is to prevent certain identity-based + * optimizations from happening, mostly for diagnostic purposes. + * + * @return a new {@link Flux} preventing {@link Publisher} / {@link Subscription} based Reactor optimizations + */ + public Flux hide() { + return new FluxHide<>(this); + } + + /** + * Keep information about the order in which source values were received by + * indexing them with a 0-based incrementing long, returning a {@link Flux} + * of {@link Tuple2 Tuple2<(index, value)>}. + *

+ * + * + * @return an indexed {@link Flux} with each source value combined with its 0-based index. + */ + public final Flux> index() { + return index(tuple2Function()); + } + + /** + * Keep information about the order in which source values were received by + * indexing them internally with a 0-based incrementing long then combining this + * information with the source value into a {@code I} using the provided {@link BiFunction}, + * returning a {@link Flux Flux<I>}. + *

+ * Typical usage would be to produce a {@link Tuple2} similar to {@link #index()}, but + * 1-based instead of 0-based: + *

+ * {@code index((i, v) -> Tuples.of(i+1, v))} + *

+ * + * + * @param indexMapper the {@link BiFunction} to use to combine elements and their index. + * @return an indexed {@link Flux} with each source value combined with its computed index. + */ + public final Flux index(BiFunction indexMapper) { + if (this instanceof Fuseable) { + return onAssembly(new FluxIndexFuseable<>(this, indexMapper)); + } + return onAssembly(new FluxIndex<>(this, indexMapper)); + } + + /** + * Ignores onNext signals (dropping them) and only propagate termination events. + * + *

+ * + *

+ * + *

Discard Support: This operator discards the upstream's elements. + * + * @return a new empty {@link Mono} representing the completion of this {@link Flux}. + */ + public final Mono ignoreElements() { + return Mono.onAssembly(new MonoIgnoreElements<>(this)); + } + + /** + * Combine values from two Publishers in case their windows overlap. Each incoming + * value triggers a creation of a new Publisher via the given {@link Function}. If the + * Publisher signals its first value or completes, the time windows for the original + * element is immediately closed. The emitted elements are obtained by passing the + * values from this {@link Flux} and the other {@link Publisher} to a {@link BiFunction}. + *

+ * There are no guarantees in what order the items get combined when multiple items from + * one or both source Publishers overlap. + * + *

+ * + * + * + * @param other the other {@link Publisher} to correlate items with + * @param leftEnd a function that returns a Publisher whose emissions indicate the + * time window for the source value to be considered + * @param rightEnd a function that returns a Publisher whose emissions indicate the + * time window for the {@code right} Publisher value to be considered + * @param resultSelector a function that takes an item emitted by each Publisher and returns the + * value to be emitted by the resulting {@link Flux} + * @param the type of the elements from the right {@link Publisher} + * @param the type for this {@link Flux} window signals + * @param the type for the right {@link Publisher} window signals + * @param the combined result type + * + * @return a joining {@link Flux} + * @see #groupJoin(Publisher, Function, Function, BiFunction) + */ + public final Flux join( + Publisher other, + Function> leftEnd, + Function> rightEnd, + BiFunction resultSelector + ) { + return onAssembly(new FluxJoin( + this, other, leftEnd, rightEnd, resultSelector)); + } + + /** + * Emit the last element observed before complete signal as a {@link Mono}, or emit + * {@link NoSuchElementException} error if the source was empty. + * For a passive version use {@link #takeLast(int)} + * + *

+ * + * + *

Discard Support: This operator discards elements before the last. + * + * @return a {@link Mono} with the last value in this {@link Flux} + */ + public final Mono last() { + if (this instanceof Callable) { + @SuppressWarnings("unchecked") + Callable thiz = (Callable) this; + Mono callableMono = wrapToMono(thiz); + if (callableMono == Mono.empty()) { + return Mono.onAssembly(new MonoError<>(new NoSuchElementException("Flux#last() didn't observe any onNext signal from Callable flux"))); + } + return Mono.onAssembly(callableMono); + } + return Mono.onAssembly(new MonoTakeLastOne<>(this)); + } + + /** + * Emit the last element observed before complete signal as a {@link Mono}, or emit + * the {@code defaultValue} if the source was empty. + * For a passive version use {@link #takeLast(int)} + * + *

+ * + * + *

Discard Support: This operator discards elements before the last. + * + * @param defaultValue a single fallback item if this {@link Flux} is empty + * + * @return a {@link Mono} with the last value in this {@link Flux} + */ + public final Mono last(T defaultValue) { + if (this instanceof Callable) { + @SuppressWarnings("unchecked") + Callable thiz = (Callable)this; + if(thiz instanceof Fuseable.ScalarCallable){ + @SuppressWarnings("unchecked") + Fuseable.ScalarCallable c = (Fuseable.ScalarCallable)thiz; + T v; + try { + v = c.call(); + } + catch (Exception e) { + return Mono.error(Exceptions.unwrap(e)); + } + if(v == null){ + return Mono.just(defaultValue); + } + return Mono.just(v); + } + Mono.onAssembly(new MonoCallable<>(thiz)); + } + return Mono.onAssembly(new MonoTakeLastOne<>(this, defaultValue)); + } + + /** + * Ensure that backpressure signals from downstream subscribers are split into batches + * capped at the provided {@code prefetchRate} when propagated upstream, effectively + * rate limiting the upstream {@link Publisher}. + *

+ * Note that this is an upper bound, and that this operator uses a prefetch-and-replenish + * strategy, requesting a replenishing amount when 75% of the prefetch amount has been + * emitted. + *

+ * 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 + * amount. + *

+ * Equivalent to {@code flux.publishOn(Schedulers.immediate(), prefetchRate).subscribe() }. + * Note that the {@code prefetchRate} is an upper bound, and that this operator uses a + * prefetch-and-replenish strategy, requesting a replenishing amount when 75% of the + * prefetch amount has been emitted. + *

+ * + * + * @param prefetchRate the limit to apply to downstream's backpressure + * + * @return a {@link Flux} limiting downstream's backpressure + * @see #publishOn(Scheduler, int) + * @see #limitRequest(long) + */ + public final Flux limitRate(int prefetchRate) { + return onAssembly(this.publishOn(Schedulers.immediate(), prefetchRate)); + } + + /** + * Ensure that backpressure signals from downstream subscribers are split into batches + * capped at the provided {@code highTide} first, then replenishing at the provided + * {@code lowTide}, effectively rate limiting the upstream {@link Publisher}. + *

+ * Note that this is an upper bound, and that this operator uses a prefetch-and-replenish + * strategy, requesting a replenishing amount when 75% of the prefetch amount has been + * emitted. + *

+ * 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 + * amount. + *

+ * Similar to {@code flux.publishOn(Schedulers.immediate(), prefetchRate).subscribe() }, + * except with a customized "low tide" instead of the default 75%. + * Note that the smaller the lowTide is, the higher the potential for concurrency + * between request and data production. And thus the more extraneous replenishment + * requests this operator could make. For example, for a global downstream + * request of 14, with a highTide of 10 and a lowTide of 2, the operator would perform + * low tide requests ({@code request(2)}) seven times in a row, whereas with the default + * lowTide of 8 it would only perform one low tide request ({@code request(8)}). + * Using a {@code lowTide} equal to {@code highTide} reverts to the default 75% strategy, + * while using a {@code lowTide} of {@literal 0} disables the lowTide, resulting in + * all requests strictly adhering to the highTide. + *

+ * + * + * @param highTide the initial request amount + * @param lowTide the subsequent (or replenishing) request amount, {@literal 0} to + * disable early replenishing, {@literal highTide} to revert to a 75% replenish strategy. + * + * @return a {@link Flux} limiting downstream's backpressure and customizing the + * replenishment request amount + * @see #publishOn(Scheduler, int) + * @see #limitRequest(long) + */ + public final Flux limitRate(int highTide, int lowTide) { + return onAssembly(this.publishOn(Schedulers.immediate(), true, highTide, lowTide)); + } + + /** + * Take only the first N values from this {@link Flux}, if available. + * Furthermore, ensure that the total amount requested upstream is capped at {@code n}. + * If n is zero, the source isn't even subscribed to and the operator completes immediately + * upon subscription. + *

+ * + *

+ * Backpressure signals from downstream subscribers are smaller than the cap are + * propagated as is, but if they would cause the total requested amount to go over the + * cap, they are reduced to the minimum value that doesn't go over. + *

+ * As a result, this operator never let the upstream produce more elements than the + * cap. + * 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). + * + * @param n the number of elements to emit from this flux, which is also the backpressure + * cap for all of downstream's request + * + * @return a {@link Flux} of {@code n} elements from the source, that requests AT MOST {@code n} from upstream in total. + * @see #take(long) + * @see #take(long, boolean) + * @deprecated replace with {@link #take(long, boolean) take(n, true)} in 3.4.x, then {@link #take(long)} in 3.5.0. + * To be removed in 3.6.0 at the earliest. See https://github.com/reactor/reactor-core/issues/2339 + */ + @Deprecated + public final Flux limitRequest(long n) { + return take(n, true); + } + + /** + * Observe all Reactive Streams signals and trace them using {@link Logger} support. + * Default will use {@link Level#INFO} and {@code java.util.logging}. + * If SLF4J is available, it will be used instead. + *

+ * + *

+ * The default log category will be "reactor.Flux.", followed by a suffix generated from + * the source operator, e.g. "reactor.Flux.Map". + * + * @return a new {@link Flux} that logs signals + */ + public final Flux log() { + return log(null, Level.INFO); + } + + /** + * Observe all Reactive Streams signals and trace them using {@link Logger} support. + * Default will use {@link Level#INFO} and {@code java.util.logging}. + * If SLF4J is available, it will be used instead. + *

+ * + * + * @param category to be mapped into logger configuration (e.g. org.springframework + * .reactor). If category ends with "." like "reactor.", a generated operator + * suffix will be added, e.g. "reactor.Flux.Map". + * + * @return a new {@link Flux} that logs signals + */ + public final Flux log(String category) { + return log(category, Level.INFO); + } + + /** + * Observe Reactive Streams signals matching the passed filter {@code options} and + * trace them using {@link Logger} support. Default will use {@link Level#INFO} and + * {@code java.util.logging}. If SLF4J is available, it will be used instead. + *

+ * Options allow fine grained filtering of the traced signal, for instance to only + * capture onNext and onError: + *

+	 *     flux.log("category", Level.INFO, SignalType.ON_NEXT, SignalType.ON_ERROR)
+	 * 
+ *

+ * + * + * @param category to be mapped into logger configuration (e.g. org.springframework + * .reactor). If category ends with "." like "reactor.", a generated operator + * suffix will be added, e.g. "reactor.Flux.Map". + * @param level the {@link Level} to enforce for this tracing Flux (only FINEST, FINE, + * INFO, WARNING and SEVERE are taken into account) + * @param options a vararg {@link SignalType} option to filter log messages + * + * @return a new {@link Flux} that logs signals + */ + public final Flux log(@Nullable String category, Level level, SignalType... options) { + return log(category, level, false, options); + } + + /** + * Observe Reactive Streams signals matching the passed filter {@code options} and + * trace them using {@link Logger} support. Default will use {@link Level#INFO} and + * {@code java.util.logging}. If SLF4J is available, it will be used instead. + *

+ * Options allow fine grained filtering of the traced signal, for instance to only + * capture onNext and onError: + *

+	 *     flux.log("category", Level.INFO, SignalType.ON_NEXT, SignalType.ON_ERROR)
+	 * 
+ *

+ * + * + * @param category to be mapped into logger configuration (e.g. org.springframework + * .reactor). If category ends with "." like "reactor.", a generated operator + * suffix will be added, e.g. "reactor.Flux.Map". + * @param level the {@link Level} to enforce for this tracing Flux (only FINEST, FINE, + * INFO, WARNING and SEVERE are taken into account) + * @param showOperatorLine capture the current stack to display operator class/line number. + * @param options a vararg {@link SignalType} option to filter log messages + * + * @return a new {@link Flux} that logs signals + */ + public final Flux log(@Nullable String category, + Level level, + boolean showOperatorLine, + SignalType... options) { + SignalLogger log = new SignalLogger<>(this, category, level, + showOperatorLine, options); + + if (this instanceof Fuseable) { + return onAssembly(new FluxLogFuseable<>(this, log)); + } + return onAssembly(new FluxLog<>(this, log)); + } + + /** + * Observe Reactive Streams signals matching the passed filter {@code options} and + * trace them using a specific user-provided {@link Logger}, at {@link Level#INFO} level. + *

+ * + * + * @param logger the {@link Logger} to use, instead of resolving one through a category. + * + * @return a new {@link Flux} that logs signals + */ + public final Flux log(Logger logger) { + return log(logger, Level.INFO, false); + } + + /** + * Observe Reactive Streams signals matching the passed filter {@code options} and + * trace them using a specific user-provided {@link Logger}, at the given {@link Level}. + *

+ * Options allow fine grained filtering of the traced signal, for instance to only + * capture onNext and onError: + *

+	 *     flux.log(myCustomLogger, Level.INFO, SignalType.ON_NEXT, SignalType.ON_ERROR)
+	 * 
+ *

+ * + * + * @param logger the {@link Logger} to use, instead of resolving one through a category. + * @param level the {@link Level} to enforce for this tracing Flux (only FINEST, FINE, + * INFO, WARNING and SEVERE are taken into account) + * @param showOperatorLine capture the current stack to display operator class/line number (default in overload is false). + * @param options a vararg {@link SignalType} option to filter log messages + * + * @return a new {@link Flux} that logs signals + */ + public final Flux log(Logger logger, + Level level, + boolean showOperatorLine, + SignalType... options) { + SignalLogger log = new SignalLogger<>(this, "IGNORED", level, + showOperatorLine, + s -> logger, + options); + + if (this instanceof Fuseable) { + return onAssembly(new FluxLogFuseable<>(this, log)); + } + return onAssembly(new FluxLog<>(this, log)); + } + + /** + * Transform the items emitted by this {@link Flux} by applying a synchronous function + * to each item. + *

+ * + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * (including when fusion is enabled). Exceptions thrown by the mapper then cause the + * source value to be dropped and a new element ({@code request(1)}) being requested + * from upstream. + * + * @param mapper the synchronous transforming {@link Function} + * + * @param the transformed type + * + * @return a transformed {@link Flux} + */ + public final Flux map(Function mapper) { + if (this instanceof Fuseable) { + return onAssembly(new FluxMapFuseable<>(this, mapper)); + } + return onAssembly(new FluxMap<>(this, mapper)); + } + + /** + * Transform the items emitted by this {@link Flux} by applying a synchronous function + * to each item, which may produce {@code null} values. In that case, no value is emitted. + * This operator effectively behaves like {@link #map(Function)} followed by {@link #filter(Predicate)} + * although {@code null} is not a supported value, so it can't be filtered out. + * + *

+ * + * + *

Error Mode Support: This operator supports {@link #onErrorContinue(BiConsumer) resuming on errors} + * (including when fusion is enabled). Exceptions thrown by the mapper then cause the + * source value to be dropped and a new element ({@code request(1)}) being requested + * from upstream. + * + * @param mapper the synchronous transforming {@link Function} + * + * @param the transformed type + * + * @return a transformed {@link Flux} + */ + public final Flux mapNotNull(Function mapper) { + return this.handle((t, sink) -> { + V v = mapper.apply(t); + if (v != null) { + sink.next(v); + } + }); + } + + /** + * Transform incoming onNext, onError and onComplete signals into {@link Signal} instances, + * materializing these signals. + * Since the error is materialized as a {@code Signal}, the propagation will be stopped and onComplete will be + * emitted. Complete signal will first emit a {@code Signal.complete()} and then effectively complete the flux. + * All these {@link Signal} have a {@link Context} associated to them. + * + *

+ * + * + * @return a {@link Flux} of materialized {@link Signal} + * @see #dematerialize() + */ + public final Flux> materialize() { + return onAssembly(new FluxMaterialize<>(this)); + } + + /** + * Merge data from this {@link Flux} and a {@link Publisher} into a reordered merge + * sequence, by picking the smallest value from each sequence as defined by a provided + * {@link Comparator}. Note that subsequent calls are combined, and their comparators are + * in lexicographic order as defined by {@link Comparator#thenComparing(Comparator)}. + *

+ * The combination step is avoided if the two {@link Comparator Comparators} are + * {@link Comparator#equals(Object) equal} (which can easily be achieved by using the + * same reference, and is also always true of {@link Comparator#naturalOrder()}). + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + *

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

+ * + * + * @param other the {@link Publisher} to merge with + * @param otherComparator the {@link Comparator} to use for merging + * + * @return a new {@link Flux} that compares latest values from the given publisher + * and this flux, using the smallest value and replenishing the source that produced it + * @deprecated Use {@link #mergeComparingWith(Publisher, Comparator)} instead + * (with the caveat that it defaults to NOT delaying errors, unlike this operator). + * To be removed in 3.6.0 at the earliest. + */ + @Deprecated + public final Flux mergeOrderedWith(Publisher other, + Comparator otherComparator) { + if (this instanceof FluxMergeComparing) { + FluxMergeComparing fluxMerge = (FluxMergeComparing) this; + return fluxMerge.mergeAdditionalSource(other, otherComparator); + } + return mergeOrdered(otherComparator, this, other); + } + + /** + * Merge data from this {@link Flux} and a {@link Publisher} into a reordered merge + * sequence, by picking the smallest value from each sequence as defined by a provided + * {@link Comparator}. Note that subsequent calls are combined, and their comparators are + * in lexicographic order as defined by {@link Comparator#thenComparing(Comparator)}. + *

+ * The combination step is avoided if the two {@link Comparator Comparators} are + * {@link Comparator#equals(Object) equal} (which can easily be achieved by using the + * same reference, and is also always true of {@link Comparator#naturalOrder()}). + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + *

+ * + *

+ * mergeComparingWith doesn't delay errors by default, but it will inherit the delayError + * behavior of a mergeComparingDelayError directly above it. + * + * @param other the {@link Publisher} to merge with + * @param otherComparator the {@link Comparator} to use for merging + * + * @return a new {@link Flux} that compares latest values from the given publisher + * and this flux, using the smallest value and replenishing the source that produced it + */ + public final Flux mergeComparingWith(Publisher other, + Comparator otherComparator) { + if (this instanceof FluxMergeComparing) { + FluxMergeComparing fluxMerge = (FluxMergeComparing) this; + return fluxMerge.mergeAdditionalSource(other, otherComparator); + } + return mergeComparing(otherComparator, this, other); + } + + /** + * Merge data from this {@link Flux} and a {@link Publisher} into an interleaved merged + * sequence. Unlike {@link #concatWith(Publisher) concat}, inner sources are subscribed + * to eagerly. + *

+ * + *

+ * Note that merge is tailored to work with asynchronous sources or finite sources. When dealing with + * an infinite source that doesn't already publish on a dedicated Scheduler, you must isolate that source + * in its own Scheduler, as merge would otherwise attempt to drain it before subscribing to + * another source. + * + * @param other the {@link Publisher} to merge with + * + * @return a new {@link Flux} + */ + public final Flux mergeWith(Publisher other) { + if (this instanceof FluxMerge) { + FluxMerge fluxMerge = (FluxMerge) this; + return fluxMerge.mergeAdditionalSource(other, Queues::get); + } + return merge(this, other); + } + + /** + * Activate metrics for this sequence, provided there is an instrumentation facade + * on the classpath (otherwise this method is a pure no-op). + *

+ * Metrics are gathered on {@link Subscriber} events, and it is recommended to also + * {@link #name(String) name} (and optionally {@link #tag(String, String) tag}) the + * sequence. + *

+ * 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)} + * 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) + */ + public final Flux metrics() { + if (!Metrics.isInstrumentationAvailable()) { + return this; + } + + if (this instanceof Fuseable) { + return onAssembly(new FluxMetricsFuseable<>(this)); + } + return onAssembly(new FluxMetrics<>(this)); + } + + /** + * 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. + * + * @param name a name for the sequence + * + * @return the same sequence, but bearing a name + * + *@see #metrics() + *@see #tag(String, String) + */ + public final Flux name(String name) { + return FluxName.createOrAppend(this, name); + } + + /** + * Emit only the first item emitted by this {@link Flux}, into a new {@link Mono}. + * If called on an empty {@link Flux}, emits an empty {@link Mono}. + *

+ * + * + * @return a new {@link Mono} emitting the first value in this {@link Flux} + */ + public final Mono next() { + if(this instanceof Callable){ + @SuppressWarnings("unchecked") + Callable m = (Callable)this; + return Mono.onAssembly(wrapToMono(m)); + } + return Mono.onAssembly(new MonoNext<>(this)); + } + + /** + * 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 + * the value is ignored and a request of 1 is emitted. + * + *

+ * + * + * @param clazz the {@link Class} type to test values against + * + * @return a new {@link Flux} filtered on items of the requested type + */ + public final Flux ofType(final Class clazz) { + Objects.requireNonNull(clazz, "clazz"); + return filter(o -> clazz.isAssignableFrom(o.getClass())).cast(clazz); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or park the + * observed elements if not enough demand is requested downstream. Errors will be + * delayed until the buffer gets consumed. + * + *

+ * + * + *

Discard Support: This operator discards the buffered overflow elements upon cancellation or error triggered by a data signal. + * + * @return a backpressured {@link Flux} that buffers with unbounded capacity + * + */ + public final Flux onBackpressureBuffer() { + return onAssembly(new FluxOnBackpressureBuffer<>(this, Queues + .SMALL_BUFFER_SIZE, true, null)); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or park up to + * {@code maxSize} elements when not enough demand is requested downstream. + * The first element past this buffer to arrive out of sync with the downstream + * subscriber's demand (the "overflowing" element) immediately triggers an overflow + * error and cancels the source. + * The {@link Flux} is going to terminate with an overflow error, but this error is + * delayed, which lets the subscriber make more requests for the content of the buffer. + *

+ * + * + *

Discard Support: This operator discards the buffered overflow elements upon cancellation or error triggered by a data signal, + * as well as elements that are rejected by the buffer due to {@code maxSize}. + * + * @param maxSize maximum number of elements overflowing request before the source is cancelled + * + * @return a backpressured {@link Flux} that buffers with bounded capacity + * + */ + public final Flux onBackpressureBuffer(int maxSize) { + return onAssembly(new FluxOnBackpressureBuffer<>(this, maxSize, false, null)); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or park up to + * {@code maxSize} elements when not enough demand is requested downstream. + * The first element past this buffer to arrive out of sync with the downstream + * subscriber's demand (the "overflowing" element) is immediately passed to a + * {@link Consumer} and the source is cancelled. + * The {@link Flux} is going to terminate with an overflow error, but this error is + * delayed, which lets the subscriber make more requests for the content of the buffer. + *

+ * Note that should the cancelled source produce further overflowing elements, these + * would be passed to the {@link Hooks#onNextDropped(Consumer) onNextDropped hook}. + *

+ * + * + *

Discard Support: This operator discards the buffered overflow elements upon cancellation or error triggered by a data signal, + * as well as elements that are rejected by the buffer due to {@code maxSize} (even though + * they are passed to the {@code onOverflow} {@link Consumer} first). + * + * @param maxSize maximum number of elements overflowing request before callback is called and source is cancelled + * @param onOverflow callback to invoke on overflow + * + * @return a backpressured {@link Flux} that buffers with a bounded capacity + * + */ + public final Flux onBackpressureBuffer(int maxSize, Consumer onOverflow) { + Objects.requireNonNull(onOverflow, "onOverflow"); + return onAssembly(new FluxOnBackpressureBuffer<>(this, maxSize, false, onOverflow)); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or park the observed + * elements if not enough demand is requested downstream, within a {@code maxSize} + * limit. Over that limit, the overflow strategy is applied (see {@link BufferOverflowStrategy}). + *

+ * Note that for the {@link BufferOverflowStrategy#ERROR ERROR} strategy, the overflow + * error will be delayed after the current backlog is consumed. + * + *

+ * + * + *

Discard Support: This operator discards the buffered overflow elements upon cancellation or error triggered by a data signal, + * as well as elements that are rejected by the buffer due to {@code maxSize} (even though + * they are passed to the {@code bufferOverflowStrategy} first). + * + * + * @param maxSize maximum buffer backlog size before overflow strategy is applied + * @param bufferOverflowStrategy strategy to apply to overflowing elements + * + * @return a backpressured {@link Flux} that buffers up to a capacity then applies an + * overflow strategy + */ + public final Flux onBackpressureBuffer(int maxSize, BufferOverflowStrategy bufferOverflowStrategy) { + Objects.requireNonNull(bufferOverflowStrategy, "bufferOverflowStrategy"); + return onAssembly(new FluxOnBackpressureBufferStrategy<>(this, maxSize, + null, bufferOverflowStrategy)); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or park the observed + * elements if not enough demand is requested downstream, within a {@code maxSize} + * limit. Over that limit, the overflow strategy is applied (see {@link BufferOverflowStrategy}). + *

+ * A {@link Consumer} is immediately invoked when there is an overflow, receiving the + * value that was discarded because of the overflow (which can be different from the + * latest element emitted by the source in case of a + * {@link BufferOverflowStrategy#DROP_LATEST DROP_LATEST} strategy). + * + *

+ * Note that for the {@link BufferOverflowStrategy#ERROR ERROR} strategy, the overflow + * error will be delayed after the current backlog is consumed. The consumer is still + * invoked immediately. + * + *

+ * + * + *

Discard Support: This operator discards the buffered overflow elements upon cancellation or error triggered by a data signal, + * as well as elements that are rejected by the buffer due to {@code maxSize} (even though + * they are passed to the {@code onOverflow} {@link Consumer} AND the {@code bufferOverflowStrategy} first). + * + * @param maxSize maximum buffer backlog size before overflow callback is called + * @param onBufferOverflow callback to invoke on overflow + * @param bufferOverflowStrategy strategy to apply to overflowing elements + * + * @return a backpressured {@link Flux} that buffers up to a capacity then applies an + * overflow strategy + */ + public final Flux onBackpressureBuffer(int maxSize, Consumer onBufferOverflow, + BufferOverflowStrategy bufferOverflowStrategy) { + Objects.requireNonNull(onBufferOverflow, "onBufferOverflow"); + Objects.requireNonNull(bufferOverflowStrategy, "bufferOverflowStrategy"); + return onAssembly(new FluxOnBackpressureBufferStrategy<>(this, maxSize, + onBufferOverflow, bufferOverflowStrategy)); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or park the observed + * elements if not enough demand is requested downstream, within a {@code maxSize} + * limit and for a maximum {@link Duration} of {@code ttl} (as measured on the + * {@link Schedulers#parallel() parallel Scheduler}). Over that limit, oldest + * elements from the source are dropped. + *

+ * Elements evicted based on the TTL are passed to a cleanup {@link Consumer}, which + * is also immediately invoked when there is an overflow. + * + *

+ * + * + *

Discard Support: This operator discards its internal buffer of elements that overflow, + * after having applied the {@code onBufferEviction} handler. + * + * @param ttl maximum {@link Duration} for which an element is kept in the backlog + * @param maxSize maximum buffer backlog size before overflow callback is called + * @param onBufferEviction callback to invoke once TTL is reached or on overflow + * + * @return a backpressured {@link Flux} that buffers with a TTL and up to a capacity then applies an + * overflow strategy + */ + public final Flux onBackpressureBuffer(Duration ttl, int maxSize, Consumer onBufferEviction) { + return onBackpressureBuffer(ttl, maxSize, onBufferEviction, Schedulers.parallel()); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or park the observed + * elements if not enough demand is requested downstream, within a {@code maxSize} + * limit and for a maximum {@link Duration} of {@code ttl} (as measured on the provided + * {@link Scheduler}). Over that limit, oldest elements from the source are dropped. + *

+ * Elements evicted based on the TTL are passed to a cleanup {@link Consumer}, which + * is also immediately invoked when there is an overflow. + * + *

+ * + * + *

Discard Support: This operator discards its internal buffer of elements that overflow, + * after having applied the {@code onBufferEviction} handler. + * + * @param ttl maximum {@link Duration} for which an element is kept in the backlog + * @param maxSize maximum buffer backlog size before overflow callback is called + * @param onBufferEviction callback to invoke once TTL is reached or on overflow + * @param scheduler the scheduler on which to run the timeout check + * + * @return a backpressured {@link Flux} that buffers with a TTL and up to a capacity then applies an + * overflow strategy + */ + public final Flux onBackpressureBuffer(Duration ttl, int maxSize, Consumer onBufferEviction, Scheduler scheduler) { + Objects.requireNonNull(ttl, "ttl"); + Objects.requireNonNull(onBufferEviction, "onBufferEviction"); + return onAssembly(new FluxOnBackpressureBufferTimeout<>(this, ttl, scheduler, maxSize, onBufferEviction)); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or drop + * the observed elements if not enough demand is requested downstream. + * + *

+ * + * + *

Discard Support: This operator discards elements that it drops. + * + * @return a backpressured {@link Flux} that drops overflowing elements + */ + public final Flux onBackpressureDrop() { + return onAssembly(new FluxOnBackpressureDrop<>(this)); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or drop and + * notify dropping {@link Consumer} with the observed elements if not enough demand + * is requested downstream. + * + *

+ * + * + *

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 + * @return a backpressured {@link Flux} that drops overflowing elements + */ + public final Flux onBackpressureDrop(Consumer onDropped) { + return onAssembly(new FluxOnBackpressureDrop<>(this, onDropped)); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or emit onError + * fom {@link Exceptions#failWithOverflow} if not enough demand is requested + * downstream. + * + *

+ * + * + *

Discard Support: This operator discards elements that it drops, after having propagated + * the error. + * + * @return a backpressured {@link Flux} that errors on overflowing elements + */ + public final Flux onBackpressureError() { + return onBackpressureDrop(t -> { throw Exceptions.failWithOverflow();}); + } + + /** + * Request an unbounded demand and push to the returned {@link Flux}, or only keep + * the most recent observed item if not enough demand is requested downstream. + * + *

+ * + *

+ *

Discard Support: Each time a new element comes in (the new "latest"), this operator + * discards the previously retained element. + * + * @return a backpressured {@link Flux} that will only keep a reference to the last observed item + */ + public final Flux onBackpressureLatest() { + return onAssembly(new FluxOnBackpressureLatest<>(this)); + } + + /** + * 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}. + * Alternatively, throwing from that biconsumer will propagate the thrown exception downstream + * in place of the original error, which is added as a suppressed exception to the new one. + *

+ * + *

+ * Note that onErrorContinue() is a specialist operator that can make the behaviour of your + * reactive chain unclear. It operates on upstream, not downstream operators, it requires specific + * operator support to work, and the scope can easily propagate upstream into library code + * that didn't anticipate it (resulting in unintended behaviour.) + *

+ * In most cases, you should instead handle the error inside the specific function which may cause + * it. Specifically, on each inner publisher you can use {@code doOnError} to log the error, and + * {@code onErrorResume(e -> Mono.empty())} to drop erroneous elements: + *

+ *

+	 * .flatMap(id -> repository.retrieveById(id)
+	 *                          .doOnError(System.err::println)
+	 *                          .onErrorResume(e -> Mono.empty()))
+	 * 
+ *

+ * This has the advantage of being much clearer, has no ambiguity with regards to operator support, + * and cannot leak upstream. + * + * @param errorConsumer a {@link BiConsumer} fed with errors matching the predicate and the value + * that triggered the error. + * @return a {@link Flux} that attempts to continue processing on errors. + */ + public final Flux onErrorContinue(BiConsumer errorConsumer) { + BiConsumer genericConsumer = errorConsumer; + return subscriberContext(Context.of( + OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, + OnNextFailureStrategy.resume(genericConsumer) + )); + } + + /** + * Let compatible operators upstream recover from errors by dropping the + * incriminating element from the sequence and continuing with subsequent elements. + * Only errors matching the specified {@code type} are recovered from. + * The recovered error and associated value are notified via the provided {@link BiConsumer}. + * Alternatively, throwing from that biconsumer will propagate the thrown exception downstream + * in place of the original error, which is added as a suppressed exception to the new one. + *

+ * + *

+ * Note that onErrorContinue() is a specialist operator that can make the behaviour of your + * reactive chain unclear. It operates on upstream, not downstream operators, it requires specific + * operator support to work, and the scope can easily propagate upstream into library code + * that didn't anticipate it (resulting in unintended behaviour.) + *

+ * In most cases, you should instead handle the error inside the specific function which may cause + * it. Specifically, on each inner publisher you can use {@code doOnError} to log the error, and + * {@code onErrorResume(e -> Mono.empty())} to drop erroneous elements: + *

+ *

+	 * .flatMap(id -> repository.retrieveById(id)
+	 *                          .doOnError(MyException.class, System.err::println)
+	 *                          .onErrorResume(MyException.class, e -> Mono.empty()))
+	 * 
+ *

+ * This has the advantage of being much clearer, has no ambiguity with regards to operator support, + * and cannot leak upstream. + * + * @param type the {@link Class} of {@link Exception} that are resumed from. + * @param errorConsumer a {@link BiConsumer} fed with errors matching the {@link Class} + * and the value that triggered the error. + * @return a {@link Flux} that attempts to continue processing on some errors. + */ + public final Flux onErrorContinue(Class type, BiConsumer errorConsumer) { + return onErrorContinue(type::isInstance, errorConsumer); + } + + /** + * Let compatible operators upstream recover from errors by dropping the + * incriminating element from the sequence and continuing with subsequent elements. + * Only errors matching the {@link Predicate} are recovered from (note that this + * predicate can be applied several times and thus must be idempotent). + * The recovered error and associated value are notified via the provided {@link BiConsumer}. + * Alternatively, throwing from that biconsumer will propagate the thrown exception downstream + * in place of the original error, which is added as a suppressed exception to the new one. + *

+ * + *

+ * Note that onErrorContinue() is a specialist operator that can make the behaviour of your + * reactive chain unclear. It operates on upstream, not downstream operators, it requires specific + * operator support to work, and the scope can easily propagate upstream into library code + * that didn't anticipate it (resulting in unintended behaviour.) + *

+ * In most cases, you should instead handle the error inside the specific function which may cause + * it. Specifically, on each inner publisher you can use {@code doOnError} to log the error, and + * {@code onErrorResume(e -> Mono.empty())} to drop erroneous elements: + *

+ *

+	 * .flatMap(id -> repository.retrieveById(id)
+	 *                          .doOnError(errorPredicate, System.err::println)
+	 *                          .onErrorResume(errorPredicate, e -> Mono.empty()))
+	 * 
+ *

+ * This has the advantage of being much clearer, has no ambiguity with regards to operator support, + * and cannot leak upstream. + * + * @param errorPredicate a {@link Predicate} used to filter which errors should be resumed from. + * This MUST be idempotent, as it can be used several times. + * @param errorConsumer a {@link BiConsumer} fed with errors matching the predicate and the value + * that triggered the error. + * @return a {@link Flux} that attempts to continue processing on some errors. + */ + public final Flux onErrorContinue(Predicate errorPredicate, + BiConsumer errorConsumer) { + //this cast is ok as only T values will be propagated in this sequence + @SuppressWarnings("unchecked") + Predicate genericPredicate = (Predicate) errorPredicate; + BiConsumer genericErrorConsumer = errorConsumer; + return subscriberContext(Context.of( + OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, + OnNextFailureStrategy.resumeIf(genericPredicate, genericErrorConsumer) + )); + } + + /** + * If an {@link #onErrorContinue(BiConsumer)} variant has been used downstream, reverts + * to the default 'STOP' mode where errors are terminal events upstream. It can be + * used for easier scoping of the on next failure strategy or to override the + * inherited strategy in a sub-stream (for example in a flatMap). It has no effect if + * {@link #onErrorContinue(BiConsumer)} has not been used downstream. + * + * @return a {@link Flux} that terminates on errors, even if {@link #onErrorContinue(BiConsumer)} + * was used downstream + */ + public final Flux onErrorStop() { + return subscriberContext(Context.of( + OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, + OnNextFailureStrategy.stop())); + } + + /** + * Transform any error emitted by this {@link Flux} by synchronously applying a function to it. + + *

+ * + * + * @param mapper the error transforming {@link Function} + * + * @return a {@link Flux} that transforms source errors to other errors + */ + public final Flux onErrorMap(Function mapper) { + return onErrorResume(e -> Mono.error(mapper.apply(e))); + } + + /** + * Transform an error emitted by this {@link Flux} by synchronously applying a function + * to it if the error matches the given type. Otherwise let the error pass through. + *

+ * + * + * @param type the class of the exception type to react to + * @param mapper the error transforming {@link Function} + * @param the error type + * + * @return a {@link Flux} that transforms some source errors to other errors + */ + public final Flux onErrorMap(Class type, + Function mapper) { + @SuppressWarnings("unchecked") + Function handler = (Function)mapper; + return onErrorMap(type::isInstance, handler); + } + + /** + * Transform an error emitted by this {@link Flux} by synchronously applying a function + * to it if the error matches the given predicate. Otherwise let the error pass through. + *

+ * + * + * @param predicate the error predicate + * @param mapper the error transforming {@link Function} + * + * @return a {@link Flux} that transforms some source errors to other errors + */ + public final Flux onErrorMap(Predicate predicate, + Function mapper) { + return onErrorResume(predicate, e -> Mono.error(mapper.apply(e))); + } + + /** + * Subscribe to a returned fallback publisher when any error occurs, using a function to + * choose the fallback depending on the error. + * + *

+ * + * + * @param fallback the function to choose the fallback to an alternative {@link Publisher} + * + * @return a {@link Flux} falling back upon source onError + */ + public final Flux onErrorResume(Function> fallback) { + return onAssembly(new FluxOnErrorResume<>(this, fallback)); + } + + /** + * Subscribe to a fallback publisher when an error matching the given type + * occurs, using a function to choose the fallback depending on the error. + *

+ * + * + * @param type the error type to match + * @param fallback the function to choose the fallback to an alternative {@link Publisher} + * @param the error type + * + * @return a {@link Flux} falling back upon source onError + */ + public final Flux onErrorResume(Class type, + Function> fallback) { + Objects.requireNonNull(type, "type"); + @SuppressWarnings("unchecked") + Function> handler = (Function>)fallback; + return onErrorResume(type::isInstance, handler); + } + + /** + * Subscribe to a fallback publisher when an error matching a given predicate + * occurs. + *

+ * + * + * @param predicate the error predicate to match + * @param fallback the function to choose the fallback to an alternative {@link Publisher} + * + * @return a {@link Flux} falling back upon source onError + */ + public final Flux onErrorResume(Predicate predicate, + Function> fallback) { + Objects.requireNonNull(predicate, "predicate"); + return onErrorResume(e -> predicate.test(e) ? fallback.apply(e) : error(e)); + } + + /** + * Simply emit a captured fallback value when any error is observed on this {@link Flux}. + *

+ * + * + * @param fallbackValue the value to emit if an error occurs + * + * @return a new falling back {@link Flux} + */ + public final Flux onErrorReturn(T fallbackValue) { + return onErrorResume(t -> just(fallbackValue)); + } + + /** + * Simply emit a captured fallback value when an error of the specified type is + * observed on this {@link Flux}. + *

+ * + * + * @param type the error type to match + * @param fallbackValue the value to emit if an error occurs that matches the type + * @param the error type + * + * @return a new falling back {@link Flux} + */ + public final Flux onErrorReturn(Class type, + T fallbackValue) { + return onErrorResume(type, t -> just(fallbackValue)); + } + + /** + * Simply emit a captured fallback value when an error matching the given predicate is + * observed on this {@link Flux}. + *

+ * + * + * @param predicate the error predicate to match + * @param fallbackValue the value to emit if an error occurs that matches the predicate + * + * @return a new falling back {@link Flux} + */ + public final Flux onErrorReturn(Predicate predicate, T fallbackValue) { + return onErrorResume(predicate, t -> just(fallbackValue)); + } + + /** + * Detaches both the child {@link Subscriber} and the {@link Subscription} on + * termination or cancellation. + *

This is an advanced interoperability operator that should help with odd + * retention scenarios when running with non-reactor {@link Subscriber}. + * + * @return a detachable {@link Flux} + */ + public final Flux onTerminateDetach() { + return new FluxDetach<>(this); + } + + + /** + * Pick the first {@link Publisher} between this {@link Flux} and another publisher + * to emit any signal (onNext/onError/onComplete) and replay all signals from that + * {@link Publisher}, effectively behaving like the fastest of these competing sources. + * + *

+ * + * + * @param other the {@link Publisher} to race with + * + * @return the fastest sequence + * @see #firstWithSignal + */ + public final Flux or(Publisher other) { + if (this instanceof FluxFirstWithSignal) { + FluxFirstWithSignal orPublisher = (FluxFirstWithSignal) this; + + FluxFirstWithSignal result = orPublisher.orAdditionalSource(other); + if (result != null) { + return result; + } + } + return firstWithSignal(this, other); + } + + /** + * Prepare this {@link Flux} by dividing data on a number of 'rails' matching the + * number of CPU cores, in a round-robin fashion. Note that to actually perform the + * work in parallel, you should call {@link ParallelFlux#runOn(Scheduler)} afterward. + * + *

+ * + * + * @return a new {@link ParallelFlux} instance + */ + public final ParallelFlux parallel() { + return parallel(Schedulers.DEFAULT_POOL_SIZE); + } + + /** + * Prepare this {@link Flux} by dividing data on a number of 'rails' matching the + * provided {@code parallelism} parameter, in a round-robin fashion. Note that to + * actually perform the work in parallel, you should call {@link ParallelFlux#runOn(Scheduler)} + * afterward. + * + *

+ * + * + * @param parallelism the number of parallel rails + * + * @return a new {@link ParallelFlux} instance + */ + public final ParallelFlux parallel(int parallelism) { + return parallel(parallelism, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Prepare this {@link Flux} by dividing data on a number of 'rails' matching the + * provided {@code parallelism} parameter, in a round-robin fashion and using a + * custom prefetch amount and queue for dealing with the source {@link Flux}'s values. + * Note that to actually perform the work in parallel, you should call + * {@link ParallelFlux#runOn(Scheduler)} afterward. + * + *

+ * + * + * @param parallelism the number of parallel rails + * @param prefetch the number of values to prefetch from the source + * + * @return a new {@link ParallelFlux} instance + */ + public final ParallelFlux parallel(int parallelism, int prefetch) { + return ParallelFlux.from(this, + parallelism, + prefetch, + Queues.get(prefetch)); + } + + /** + * Prepare a {@link ConnectableFlux} which shares this {@link Flux} sequence and + * dispatches values to subscribers in a backpressure-aware manner. Prefetch will + * default to {@link Queues#SMALL_BUFFER_SIZE}. This will effectively turn + * any type of sequence into a hot sequence. + *

+ * Backpressure will be coordinated on {@link Subscription#request} and if any + * {@link Subscriber} is missing demand (requested = 0), multicast will pause + * pushing/pulling. + *

+ * + * + * @return a new {@link ConnectableFlux} + */ + public final ConnectableFlux publish() { + return publish(Queues.SMALL_BUFFER_SIZE); + } + + /** + * Prepare a {@link ConnectableFlux} which shares this {@link Flux} sequence and + * dispatches values to subscribers in a backpressure-aware manner. This will + * effectively turn any type of sequence into a hot sequence. + *

+ * Backpressure will be coordinated on {@link Subscription#request} and if any + * {@link Subscriber} is missing demand (requested = 0), multicast will pause + * pushing/pulling. + *

+ * + * + * @param prefetch bounded requested demand + * + * @return a new {@link ConnectableFlux} + */ + public final ConnectableFlux publish(int prefetch) { + return onAssembly(new FluxPublish<>(this, prefetch, Queues + .get(prefetch))); + } + + /** + * Shares a sequence for the duration of a function that may transform it and + * consume it as many times as necessary without causing multiple subscriptions + * to the upstream. + * + * @param transform the transformation function + * @param the output value type + * + * @return a new {@link Flux} + */ + public final Flux publish(Function, ? extends Publisher> transform) { + return publish(transform, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Shares a sequence for the duration of a function that may transform it and + * consume it as many times as necessary without causing multiple subscriptions + * to the upstream. + * + * @param transform the transformation function + * @param prefetch the request size + * @param the output value type + * + * @return a new {@link Flux} + */ + public final Flux publish(Function, ? extends Publisher> transform, int prefetch) { + return onAssembly(new FluxPublishMulticast<>(this, transform, prefetch, Queues + .get(prefetch))); + } + + /** + * Prepare a {@link Mono} which shares this {@link Flux} sequence and dispatches the + * first observed item to subscribers in a backpressure-aware manner. + * This will effectively turn any type of sequence into a hot sequence when the first + * {@link Subscriber} subscribes. + *

+ * + * + * @return a new {@link Mono} + * @deprecated use {@link #shareNext()} instead, or use `publish().next()` if you need + * to `{@link ConnectableFlux#connect() connect()}. To be removed in 3.5.0 + */ + @Deprecated + public final Mono publishNext() { + //Should add a ConnectableMono to align with #publish() + return shareNext(); + } + + /** + * Run onNext, onComplete and onError on a supplied {@link Scheduler} + * {@link Worker Worker}. + *

+ * This operator influences the threading context where the rest of the operators in + * the chain below it will execute, up to a new occurrence of {@code publishOn}. + *

+ * + *

+ * Typically used for fast publisher, slow consumer(s) scenarios. + *

+	 * {@code flux.publishOn(Schedulers.single()).subscribe() }
+	 * 
+ * + *

Discard Support: This operator discards elements it internally queued for backpressure upon cancellation or error triggered by a data signal. + * + * @param scheduler a {@link Scheduler} providing the {@link Worker} where to publish + * + * @return a {@link Flux} producing asynchronously on a given {@link Scheduler} + */ + public final Flux publishOn(Scheduler scheduler) { + return publishOn(scheduler, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Run onNext, onComplete and onError on a supplied {@link Scheduler} + * {@link Worker}. + *

+ * This operator influences the threading context where the rest of the operators in + * the chain below it will execute, up to a new occurrence of {@code publishOn}. + *

+ * + *

+ * Typically used for fast publisher, slow consumer(s) scenarios. + *

+	 * {@code flux.publishOn(Schedulers.single()).subscribe() }
+	 * 
+ * + *

Discard Support: This operator discards elements it internally queued for backpressure upon cancellation or error triggered by a data signal. + * + * @param scheduler a {@link Scheduler} providing the {@link Worker} where to publish + * @param prefetch the asynchronous boundary capacity + * + * @return a {@link Flux} producing asynchronously + */ + public final Flux publishOn(Scheduler scheduler, int prefetch) { + return publishOn(scheduler, true, prefetch); + } + + /** + * Run onNext, onComplete and onError on a supplied {@link Scheduler} + * {@link Worker}. + *

+ * This operator influences the threading context where the rest of the operators in + * the chain below it will execute, up to a new occurrence of {@code publishOn}. + *

+ * + *

+ * Typically used for fast publisher, slow consumer(s) scenarios. + *

+	 * {@code flux.publishOn(Schedulers.single()).subscribe() }
+	 * 
+ * + *

Discard Support: This operator discards elements it internally queued for backpressure upon cancellation or error triggered by a data signal. + * + * @param scheduler a {@link Scheduler} providing the {@link Worker} where to publish + * @param delayError should the buffer be consumed before forwarding any error + * @param prefetch the asynchronous boundary capacity + * + * @return a {@link Flux} producing asynchronously + */ + public final Flux publishOn(Scheduler scheduler, boolean delayError, int prefetch) { + return publishOn(scheduler, delayError, prefetch, prefetch); + } + + final Flux publishOn(Scheduler scheduler, boolean delayError, int prefetch, int lowTide) { + if (this instanceof Callable) { + if (this instanceof Fuseable.ScalarCallable) { + @SuppressWarnings("unchecked") + Fuseable.ScalarCallable s = (Fuseable.ScalarCallable) this; + try { + return onAssembly(new FluxSubscribeOnValue<>(s.call(), scheduler)); + } + catch (Exception e) { + //leave FluxSubscribeOnCallable defer exception call + } + } + @SuppressWarnings("unchecked") + Callable c = (Callable)this; + return onAssembly(new FluxSubscribeOnCallable<>(c, scheduler)); + } + + return onAssembly(new FluxPublishOn<>(this, scheduler, delayError, prefetch, lowTide, Queues.get(prefetch))); + } + + /** + * Reduce the values from this {@link Flux} sequence into a single object of the same + * type than the emitted items. Reduction is performed using a {@link BiFunction} that + * takes the intermediate result of the reduction and the current value and returns + * the next intermediate value of the reduction. Note, {@link BiFunction} will not + * be invoked for a sequence with 0 or 1 elements. In case of one element's + * sequence, the result will be directly sent to the subscriber. + * + *

+ * + * + *

Discard Support: This operator discards the internally accumulated value upon cancellation or error. + * + * @param aggregator the reducing {@link BiFunction} + * + * @return a reduced {@link Flux} + */ + public final Mono reduce(BiFunction aggregator) { + if (this instanceof Callable){ + @SuppressWarnings("unchecked") + Callable thiz = (Callable)this; + return Mono.onAssembly(wrapToMono(thiz)); + } + return Mono.onAssembly(new MonoReduce<>(this, aggregator)); + } + + /** + * Reduce the values from this {@link Flux} sequence into a single object matching the + * type of a seed value. Reduction is performed using a {@link BiFunction} that + * takes the intermediate result of the reduction and the current value and returns + * the next intermediate value of the reduction. First element is paired with the seed + * value, {@literal initial}. + * + *

+ * + * + *

Discard Support: This operator discards the internally accumulated value upon cancellation or error. + * + * @param accumulator the reducing {@link BiFunction} + * @param initial the seed, the initial leftmost argument to pass to the reducing {@link BiFunction} + * @param the type of the seed and the reduced object + * + * @return a reduced {@link Flux} + * + */ + public final Mono reduce(A initial, BiFunction accumulator) { + return reduceWith(() -> initial, accumulator); + } + + /** + * Reduce the values from this {@link Flux} sequence into a single object matching the + * type of a lazily supplied seed value. Reduction is performed using a + * {@link BiFunction} that takes the intermediate result of the reduction and the + * current value and returns the next intermediate value of the reduction. First + * element is paired with the seed value, supplied via {@literal initial}. + * + *

+ * + * + *

Discard Support: This operator discards the internally accumulated value upon cancellation or error. + * + * @param accumulator the reducing {@link BiFunction} + * @param initial a {@link Supplier} of the seed, called on subscription and passed to the the reducing {@link BiFunction} + * @param the type of the seed and the reduced object + * + * @return a reduced {@link Flux} + * + */ + public final Mono reduceWith(Supplier initial, BiFunction accumulator) { + return Mono.onAssembly(new MonoReduceSeed<>(this, initial, accumulator)); + } + + /** + * Repeatedly and indefinitely subscribe to the source upon completion of the + * previous subscription. + * + *

+ * + * + * @return an indefinitely repeated {@link Flux} on onComplete + */ + public final Flux repeat() { + return repeat(ALWAYS_BOOLEAN_SUPPLIER); + } + + /** + * Repeatedly subscribe to the source if the predicate returns true after completion of the previous subscription. + * + *

+ * + * + * @param predicate the boolean to evaluate on onComplete. + * + * @return a {@link Flux} that repeats on onComplete while the predicate matches + */ + public final Flux repeat(BooleanSupplier predicate) { + return onAssembly(new FluxRepeatPredicate<>(this, predicate)); + } + + /** + * Repeatedly subscribe to the source {@code numRepeat} times. This results in + * {@code numRepeat + 1} total subscriptions to the original source. As a consequence, + * using 0 plays the original sequence once. + * + *

+ * + * + * @param numRepeat the number of times to re-subscribe on onComplete (positive, or 0 for original sequence only) + * + * @return a {@link Flux} that repeats on onComplete, up to the specified number of repetitions + */ + public final Flux repeat(long numRepeat) { + if(numRepeat == 0L){ + return this; + } + return onAssembly(new FluxRepeat<>(this, numRepeat)); + } + + /** + * Repeatedly subscribe to the source if the predicate returns true after completion of the previous + * subscription. A specified maximum of repeat will limit the number of re-subscribe. + * + *

+ * + * + * @param numRepeat the number of times to re-subscribe on complete (positive, or 0 for original sequence only) + * @param predicate the boolean to evaluate on onComplete + * + * @return a {@link Flux} that repeats on onComplete while the predicate matches, + * up to the specified number of repetitions + */ + public final Flux repeat(long numRepeat, BooleanSupplier predicate) { + if (numRepeat < 0L) { + throw new IllegalArgumentException("numRepeat >= 0 required"); + } + if (numRepeat == 0) { + return this; + } + return defer( () -> repeat(countingBooleanSupplier(predicate, numRepeat))); + } + + /** + * Repeatedly subscribe to this {@link Flux} when a companion sequence emits elements in + * response to the flux completion signal. Any terminal signal from the companion + * sequence will terminate the resulting {@link Flux} with the same signal immediately. + *

If the companion sequence signals when this {@link Flux} is active, the repeat + * attempt is suppressed. + *

+ * + *

+ * Note that if the companion {@link Publisher} created by the {@code repeatFactory} + * emits {@link Context} as trigger objects, these {@link Context} will be merged with + * the previous Context: + *


+	 * .repeatWhen(emittedEachAttempt -> emittedEachAttempt.handle((lastEmitted, sink) -> {
+	 * 	    Context ctx = sink.currentContext();
+	 * 	    int rl = ctx.getOrDefault("repeatsLeft", 0);
+	 * 	    if (rl > 0) {
+	 *		    sink.next(Context.of(
+	 *		        "repeatsLeft", rl - 1,
+	 *		        "emitted", lastEmitted
+	 *		    ));
+	 * 	    } else {
+	 * 	        sink.error(new IllegalStateException("repeats exhausted"));
+	 * 	    }
+	 * }))
+	 * 
+ * + * @param repeatFactory the {@link Function} that returns the associated {@link Publisher} + * companion, given a {@link Flux} that signals each onComplete as a {@link Long} + * representing the number of source elements emitted in the latest attempt. + * + * @return a {@link Flux} that repeats on onComplete when the companion {@link Publisher} produces an + * onNext signal + */ + public final Flux repeatWhen(Function, ? extends Publisher> repeatFactory) { + return onAssembly(new FluxRepeatWhen<>(this, repeatFactory)); + } + + /** + * Turn this {@link Flux} into a hot source and cache last emitted signals for further {@link Subscriber}. Will + * retain an unbounded amount of onNext signals. Completion and Error will also be + * replayed. + *

+ * + * + * @return a replaying {@link ConnectableFlux} + */ + public final ConnectableFlux replay() { + return replay(Integer.MAX_VALUE); + } + + /** + * Turn this {@link Flux} into a connectable hot source and cache last emitted + * signals for further {@link Subscriber}. + * 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 + * expiration. + * + *

+ * + * + * @param history number of events retained in history excluding complete and + * error + * + * @return a replaying {@link ConnectableFlux} + * + */ + 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 FluxReplay<>(this, history, 0L, null)); + } + + /** + * Turn this {@link Flux} into a connectable hot source and cache last emitted signals + * for further {@link Subscriber}. Will retain each onNext up to the given per-item + * expiry timeout. + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription + * + *

+ * + * + * @param ttl Per-item and post termination timeout duration + * + * @return a replaying {@link ConnectableFlux} + */ + public final ConnectableFlux replay(Duration ttl) { + return replay(Integer.MAX_VALUE, ttl); + } + + /** + * Turn this {@link Flux} into a connectable hot source and cache last emitted signals + * for further {@link Subscriber}. Will retain up to the given history size onNext + * signals with a per-item ttl. + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription + * + *

+ * + * + * @param history number of events retained in history excluding complete and error + * @param ttl Per-item and post termination timeout duration + * + * @return a replaying {@link ConnectableFlux} + */ + public final ConnectableFlux replay(int history, Duration ttl) { + return replay(history, ttl, Schedulers.parallel()); + } + + /** + * Turn this {@link Flux} into a connectable hot source and cache last emitted signals + * for further {@link Subscriber}. Will retain onNext signal for up to the given + * {@link Duration} with a per-item ttl. + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription + *

+ * + * + * @param ttl Per-item and post termination timeout duration + * @param timer a time-capable {@link Scheduler} instance to read current time from + * + * @return a replaying {@link ConnectableFlux} + */ + public final ConnectableFlux replay(Duration ttl, Scheduler timer) { + return replay(Integer.MAX_VALUE, ttl, timer); + } + + /** + * Turn this {@link Flux} into a connectable hot source and cache last emitted signals + * for further {@link Subscriber}. Will retain up to the given history size onNext + * signals with a per-item ttl. + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription + *

+ * + * + * @param history number of events retained in history excluding complete and error + * @param ttl Per-item and post termination timeout duration + * @param timer a {@link Scheduler} instance to read current time from + * + * @return a replaying {@link ConnectableFlux} + */ + 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 FluxReplay<>(this, history, ttl.toNanos(), timer)); + } + + /** + * Re-subscribes to this {@link Flux} sequence if it signals any error, indefinitely. + *

+ * + * + * @return a {@link Flux} that retries on onError + */ + public final Flux retry() { + return retry(Long.MAX_VALUE); + } + + /** + * Re-subscribes to this {@link Flux} sequence if it signals any error, for a fixed + * number of times. + *

+ * Note that passing {@literal Long.MAX_VALUE} is treated as infinite retry. + *

+ * + * + * @param numRetries the number of times to tolerate an error + * + * @return a {@link Flux} that retries on onError up to the specified number of retry attempts. + * + */ + public final Flux retry(long numRetries) { + return onAssembly(new FluxRetry<>(this, numRetries)); + } + + /** + * Retries this {@link Flux} in response to signals emitted by a companion {@link Publisher}. + * 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} + * 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). + *

+ * Terminal signals in the companion terminate the sequence with the same signal, so emitting an {@link Subscriber#onError(Throwable)} + * will fail the resulting {@link Flux} with that same error. + *

+ * + *

+ * Note that the {@link reactor.util.retry.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. + *

+ * 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 + * the previous Context: + *

+	 * {@code
+	 * Retry customStrategy = Retry.from(companion -> companion.handle((retrySignal, sink) -> {
+	 * 	    Context ctx = sink.currentContext();
+	 * 	    int rl = ctx.getOrDefault("retriesLeft", 0);
+	 * 	    if (rl > 0) {
+	 *		    sink.next(Context.of(
+	 *		        "retriesLeft", rl - 1,
+	 *		        "lastError", retrySignal.failure()
+	 *		    ));
+	 * 	    } else {
+	 * 	        sink.error(Exceptions.retryExhausted("retries exhausted", retrySignal.failure()));
+	 * 	    }
+	 * }));
+	 * Flux retried = originalFlux.retryWhen(customStrategy);
+	 * }
+ *
+ * + * @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}. + * + * @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) + */ + public final Flux retryWhen(Retry retrySpec) { + return onAssembly(new FluxRetryWhen<>(this, retrySpec)); + } + + /** + * Sample this {@link Flux} by periodically emitting an item corresponding to that + * {@link Flux} latest emitted value within the periodical time window. + * Note that if some elements are emitted quicker than the timespan just before source + * completion, the last of these elements will be emitted along with the onComplete + * signal. + * + *

+ * + * + *

Discard Support: This operator discards elements that are not part of the sampling. + * + * @param timespan the duration of the window after which to emit the latest observed item + * + * @return a {@link Flux} sampled to the last item seen over each periodic window + */ + public final Flux sample(Duration timespan) { + return sample(interval(timespan)); + } + + /** + * Sample this {@link Flux} by emitting an item corresponding to that {@link Flux} + * latest emitted value whenever a companion sampler {@link Publisher} signals a value. + *

+ * Termination of either {@link Publisher} will result in termination for the {@link Subscriber} + * as well. + * Note that if some elements are emitted just before source completion and before a + * last sampler can trigger, the last of these elements will be emitted along with the + * onComplete signal. + *

+ * Both {@link Publisher} will run in unbounded mode because the backpressure + * would interfere with the sampling precision. + * + *

+ * + * + *

Discard Support: This operator discards elements that are not part of the sampling. + * + * @param sampler the sampler companion {@link Publisher} + * + * @param the type of the sampler sequence + * + * @return a {@link Flux} sampled to the last item observed each time the sampler {@link Publisher} signals + */ + public final Flux sample(Publisher sampler) { + return onAssembly(new FluxSample<>(this, sampler)); + } + + /** + * Repeatedly take a value from this {@link Flux} then skip the values that follow + * within a given duration. + * + *

+ * + * + *

Discard Support: This operator discards elements that are not part of the sampling. + * + * @param timespan the duration during which to skip values after each sample + * + * @return a {@link Flux} sampled to the first item of each duration-based window + */ + public final Flux sampleFirst(Duration timespan) { + return sampleFirst(t -> Mono.delay(timespan)); + } + + /** + * Repeatedly take a value from this {@link Flux} then skip the values that follow + * before the next signal from a companion sampler {@link Publisher}. + * + *

+ * + * + *

Discard Support: This operator discards elements that are not part of the sampling. + * + * @param samplerFactory supply a companion sampler {@link Publisher} which signals the end of the skip window + * @param the companion reified type + * + * @return a {@link Flux} sampled to the first item observed in each window closed by the sampler signals + */ + public final Flux sampleFirst(Function> samplerFactory) { + return onAssembly(new FluxSampleFirst<>(this, samplerFactory)); + } + + /** + * Emit the latest value from this {@link Flux} only if there were no new values emitted + * during the window defined by a companion {@link Publisher} derived from that particular + * value. + *

+ * Note that this means that the last value in the sequence is always emitted. + * + *

+ * + * + *

Discard Support: This operator discards elements that are not part of the sampling. + * + * @param throttlerFactory supply a companion sampler {@link Publisher} which signals + * the end of the window during which no new emission should occur. If it is the case, + * the original value triggering the window is emitted. + * @param the companion reified type + * + * @return a {@link Flux} sampled to items not followed by any other item within a window + * defined by a companion {@link Publisher} + */ + public final Flux sampleTimeout(Function> throttlerFactory) { + return sampleTimeout(throttlerFactory, Queues.XS_BUFFER_SIZE); + } + + /** + * Emit the latest value from this {@link Flux} only if there were no new values emitted + * during the window defined by a companion {@link Publisher} derived from that particular + * value. + *

The provided {@literal maxConcurrency} will keep a bounded maximum of concurrent timeouts and drop any new + * items until at least one timeout terminates. + *

+ * Note that this means that the last value in the sequence is always emitted. + * + *

+ * + * + *

Discard Support: This operator discards elements that are not part of the sampling. + * + * @param throttlerFactory supply a companion sampler {@link Publisher} which signals + * the end of the window during which no new emission should occur. If it is the case, + * the original value triggering the window is emitted. + * @param maxConcurrency the maximum number of concurrent timeouts + * @param the throttling type + * + * @return a {@link Flux} sampled to items not followed by any other item within a window + * defined by a companion {@link Publisher} + */ + //FIXME re-evaluate the wording on maxConcurrency, seems more related to request/buffering. If so redo the marble + public final Flux sampleTimeout(Function> + throttlerFactory, int maxConcurrency) { + return onAssembly(new FluxSampleTimeout<>(this, throttlerFactory, + Queues.get(maxConcurrency))); + } + + /** + * Reduce this {@link Flux} values with an accumulator {@link BiFunction} and + * also emit the intermediate results of this function. + *

+ * Unlike {@link #scan(Object, BiFunction)}, this operator doesn't take an initial value + * but treats the first {@link Flux} value as initial value. + *
+ * The accumulation works as follows: + *


+	 * result[0] = source[0]
+	 * result[1] = accumulator(result[0], source[1])
+	 * result[2] = accumulator(result[1], source[2])
+	 * result[3] = accumulator(result[2], source[3])
+	 * ...
+	 * 
+ * + *

+ * + * + * @param accumulator the accumulating {@link BiFunction} + * + * @return an accumulating {@link Flux} + */ + public final Flux scan(BiFunction accumulator) { + return onAssembly(new FluxScan<>(this, accumulator)); + } + + /** + * Reduce this {@link Flux} values with an accumulator {@link BiFunction} and + * also emit the intermediate results of this function. + *

+ * The accumulation works as follows: + *


+	 * result[0] = initialValue;
+	 * result[1] = accumulator(result[0], source[0])
+	 * result[2] = accumulator(result[1], source[1])
+	 * result[3] = accumulator(result[2], source[2])
+	 * ...
+	 * 
+ * + *

+ * + * + * @param initial the initial leftmost argument to pass to the reduce function + * @param accumulator the accumulating {@link BiFunction} + * @param the accumulated type + * + * @return an accumulating {@link Flux} starting with initial state + * + */ + public final Flux scan(A initial, BiFunction accumulator) { + Objects.requireNonNull(initial, "seed"); + return scanWith(() -> initial, accumulator); + } + + /** + * Reduce this {@link Flux} values with the help of an accumulator {@link BiFunction} + * and also emits the intermediate results. A seed value is lazily provided by a + * {@link Supplier} invoked for each {@link Subscriber}. + *

+ * The accumulation works as follows: + *


+	 * result[0] = initialValue;
+	 * result[1] = accumulator(result[0], source[0])
+	 * result[2] = accumulator(result[1], source[1])
+	 * result[3] = accumulator(result[2], source[2])
+	 * ...
+	 * 
+ * + *

+ * + * + * @param initial the supplier providing the seed, the leftmost parameter initially + * passed to the reduce function + * @param accumulator the accumulating {@link BiFunction} + * @param the accumulated type + * + * @return an accumulating {@link Flux} starting with initial state + * + */ + public final Flux scanWith(Supplier initial, BiFunction + accumulator) { + return onAssembly(new FluxScanSeed<>(this, initial, accumulator)); + } + + /** + * Returns a new {@link Flux} that multicasts (shares) the original {@link Flux}. + * As long as there is at least one {@link Subscriber} this {@link Flux} will be subscribed and + * emitting data. + * When all subscribers have cancelled it will cancel the source + * {@link Flux}. + *

This is an alias for {@link #publish()}.{@link ConnectableFlux#refCount()}. + * + * @return a {@link Flux} that upon first subscribe causes the source {@link Flux} + * 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) + ); + } + + /** + * Prepare a {@link Mono} which shares this {@link Flux} sequence and dispatches the + * first observed item to subscribers. + * This will effectively turn any type of sequence into a hot sequence when the first + * {@link Subscriber} subscribes. + *

+ * + * + * @return a new {@link Mono} + */ + public final Mono shareNext() { + final NextProcessor nextProcessor = new NextProcessor<>(this); + return Mono.onAssembly(nextProcessor); + } + + /** + * Expect and emit a single item from this {@link Flux} source or signal + * {@link java.util.NoSuchElementException} for an empty source, or + * {@link IndexOutOfBoundsException} for a source with more than one element. + * + *

+ * + * + * @return a {@link Mono} with the single item or an error signal + */ + public final Mono single() { + 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)); + } + if (v == null) { + return Mono.error(new NoSuchElementException("Source was a (constant) empty")); + } + return Mono.just(v); + } + @SuppressWarnings("unchecked") + Callable thiz = (Callable)this; + return Mono.onAssembly(new MonoSingleCallable<>(thiz)); + } + return Mono.onAssembly(new MonoSingle<>(this)); + } + + /** + * Expect and emit a single item from this {@link Flux} source and emit a default + * value for an empty source, but signal an {@link IndexOutOfBoundsException} for a + * source with more than one element. + * + *

+ * + * + * @param defaultValue a single fallback item if this {@link Flux} is empty + * + * @return a {@link Mono} with the expected single item, the supplied default value or + * an error signal + */ + public final Mono single(T defaultValue) { + 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)); + } + if (v == null) { + return Mono.just(defaultValue); + } + return Mono.just(v); + } + @SuppressWarnings("unchecked") + Callable thiz = (Callable)this; + return Mono.onAssembly(new MonoSingleCallable<>(thiz, defaultValue)); + } + return Mono.onAssembly(new MonoSingle<>(this, defaultValue, false)); + } + + /** + * Expect and emit a single item from this {@link Flux} source, and accept an empty + * source but signal an {@link IndexOutOfBoundsException} for a source with more than + * one element. + *

+ * + * + * @return a {@link Mono} with the expected single item, no item or an error + */ + public final Mono singleOrEmpty() { + if (this instanceof Callable) { + @SuppressWarnings("unchecked") + Callable thiz = (Callable)this; + return Mono.onAssembly(wrapToMono(thiz)); + } + return Mono.onAssembly(new MonoSingle<>(this, null, true)); + } + + /** + * Skip the specified number of elements from the beginning of this {@link Flux} then + * emit the remaining elements. + * + *

+ * + * + *

Discard Support: This operator discards elements that are skipped. + * + * @param skipped the number of elements to drop + * + * @return a dropping {@link Flux} with the specified number of elements skipped at + * the beginning + */ + public final Flux skip(long skipped) { + if (skipped == 0L) { + return this; + } + else { + return onAssembly(new FluxSkip<>(this, skipped)); + } + } + + /** + * Skip elements from this {@link Flux} emitted within the specified initial duration. + * + *

+ * + * + *

Discard Support: This operator discards elements that are skipped. + * + * @param timespan the initial time window during which to drop elements + * + * @return a {@link Flux} dropping at the beginning until the end of the given duration + */ + public final Flux skip(Duration timespan) { + return skip(timespan, Schedulers.parallel()); + } + + /** + * Skip elements from this {@link Flux} emitted within the specified initial duration, + * as measured on the provided {@link Scheduler}. + * + *

+ * + * + *

Discard Support: This operator discards elements that are skipped. + * + * @param timespan the initial time window during which to drop elements + * @param timer a time-capable {@link Scheduler} instance to measure the time window on + * + * @return a {@link Flux} dropping at the beginning for the given duration + */ + public final Flux skip(Duration timespan, Scheduler timer) { + if(!timespan.isZero()) { + return skipUntilOther(Mono.delay(timespan, timer)); + } + else{ + return this; + } + } + + /** + * Skip a specified number of elements at the end of this {@link Flux} sequence. + * + *

+ * + * + *

Discard Support: This operator discards elements that are skipped. + * + * @param n the number of elements to drop before completion + * + * @return a {@link Flux} dropping the specified number of elements at the end of the + * sequence + * + */ + public final Flux skipLast(int n) { + if (n == 0) { + return this; + } + return onAssembly(new FluxSkipLast<>(this, n)); + } + + /** + * Skips values from this {@link Flux} until a {@link Predicate} returns true for the + * value. The resulting {@link Flux} will include and emit the matching value. + * + *

+ * + * + *

Discard Support: This operator discards elements that are skipped. + * + * @param untilPredicate the {@link Predicate} evaluated to stop skipping. + * + * @return a {@link Flux} dropping until the {@link Predicate} matches + */ + public final Flux skipUntil(Predicate untilPredicate) { + return onAssembly(new FluxSkipUntil<>(this, untilPredicate)); + } + + /** + * Skip values from this {@link Flux} until a specified {@link Publisher} signals + * an onNext or onComplete. + * + *

+ * + * + *

Discard Support: This operator discards elements that are skipped. + * + * @param other the companion {@link Publisher} to coordinate with to stop skipping + * + * @return a {@link Flux} dropping until the other {@link Publisher} emits + * + */ + public final Flux skipUntilOther(Publisher other) { + return onAssembly(new FluxSkipUntilOther<>(this, other)); + } + + /** + * Skips values from this {@link Flux} while a {@link Predicate} returns true for the value. + * + *

+ * + * + *

Discard Support: This operator discards elements that are skipped. + * + * @param skipPredicate the {@link Predicate} that causes skipping while evaluating to true. + * + * @return a {@link Flux} dropping while the {@link Predicate} matches + */ + public final Flux skipWhile(Predicate skipPredicate) { + return onAssembly(new FluxSkipWhile<>(this, skipPredicate)); + } + + /** + * Sort elements from this {@link Flux} by collecting and sorting them in the background + * then emitting the sorted sequence once this sequence completes. + * Each item emitted by the {@link Flux} must implement {@link Comparable} with + * respect to all other items in the sequence. + * + *

Note that calling {@code sort} with long, non-terminating or infinite sources + * might cause {@link OutOfMemoryError}. Use sequence splitting like {@link #window} to sort batches in that case. + *

+ * + * + * @throws ClassCastException if any item emitted by the {@link Flux} does not implement + * {@link Comparable} with respect to all other items emitted by the {@link Flux} + * @return a sorted {@link Flux} + */ + public final Flux sort(){ + return collectSortedList().flatMapIterable(identityFunction()); + } + + /** + * Sort elements from this {@link Flux} using a {@link Comparator} function, by + * collecting and sorting elements in the background then emitting the sorted sequence + * once this sequence completes. + * + *

Note that calling {@code sort} with long, non-terminating or infinite sources + * might cause {@link OutOfMemoryError} + *

+ * + * + * @param sortFunction a function that compares two items emitted by this {@link Flux} + * to indicate their sort order + * @return a sorted {@link Flux} + */ + public final Flux sort(Comparator sortFunction) { + return collectSortedList(sortFunction).flatMapIterable(identityFunction()); + } + + /** + * Prepend the given {@link Iterable} before this {@link Flux} sequence. + * + *

+ * + * + * @param iterable the sequence of values to start the resulting {@link Flux} with + * + * @return a new {@link Flux} prefixed with elements from an {@link Iterable} + */ + public final Flux startWith(Iterable iterable) { + return startWith(fromIterable(iterable)); + } + + /** + * Prepend the given values before this {@link Flux} sequence. + * + *

+ * + * + * @param values the array of values to start the resulting {@link Flux} with + * + * @return a new {@link Flux} prefixed with the given elements + */ + @SafeVarargs + public final Flux startWith(T... values) { + return startWith(just(values)); + } + + /** + * Prepend the given {@link Publisher} sequence to this {@link Flux} sequence. + * + *

+ * + * + * @param publisher the Publisher whose values to prepend + * + * @return a new {@link Flux} prefixed with the given {@link Publisher} sequence + */ + public final Flux startWith(Publisher publisher) { + if (this instanceof FluxConcatArray) { + FluxConcatArray fluxConcatArray = (FluxConcatArray) this; + return fluxConcatArray.concatAdditionalSourceFirst(publisher); + } + return concat(publisher, this); + } + + /** + * Subscribe to this {@link Flux} and request unbounded demand. + *

+ * This version doesn't specify any consumption behavior for the events from the + * chain, especially no error handling, so other variants should usually be preferred. + * + *

+ * + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe() { + return subscribe(null, null, null); + } + + /** + * 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 version that gives you more control over backpressure and the request, see + * {@link #subscribe(Subscriber)} with a {@link BaseSubscriber}. + *

+ * 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 + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each value (onNext signal) + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe(Consumer consumer) { + Objects.requireNonNull(consumer, "consumer"); + return subscribe(consumer, null, null); + } + + /** + * Subscribe to this {@link Flux} with a {@link Consumer} that will consume all the + * elements in the sequence, as well as a {@link Consumer} that will handle errors. + * 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)}. + *

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

+ * 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 consumers are + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each next signal + * @param errorConsumer the consumer to invoke on error signal + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe(@Nullable Consumer consumer, Consumer errorConsumer) { + Objects.requireNonNull(errorConsumer, "errorConsumer"); + return subscribe(consumer, errorConsumer, null); + } + + /** + * Subscribe {@link Consumer} to this {@link Flux} that will respectively consume all the + * 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 version that gives you more control over backpressure and the request, see + * {@link #subscribe(Subscriber)} with a {@link BaseSubscriber}. + *

+ * 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 + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each value + * @param errorConsumer the consumer to invoke on error signal + * @param completeConsumer the consumer to invoke on complete signal + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe( + @Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer) { + return subscribe(consumer, errorConsumer, completeConsumer, (Context) null); + } + + /** + * Subscribe {@link Consumer} to this {@link Flux} that will respectively consume all the + * elements in the sequence, handle errors, react to completion, and request upon subscription. + * It will let the provided {@link Subscription subscriptionConsumer} + * 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)} + * 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}. + *

+ * 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 + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each value + * @param errorConsumer the consumer to invoke on error signal + * @param completeConsumer the consumer to invoke on complete signal + * @param subscriptionConsumer the consumer to invoke on subscribe signal, to be used + * for the initial {@link Subscription#request(long) request}, or null for max request + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + * @deprecated Because users tend to forget to {@link Subscription#request(long) request} the subsciption. If + * the behavior is really needed, consider using {@link #subscribeWith(Subscriber)}. To be removed in 3.5. + */ + @Deprecated + public final Disposable subscribe( + @Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer, + @Nullable Consumer subscriptionConsumer) { + return subscribeWith(new LambdaSubscriber<>(consumer, errorConsumer, + completeConsumer, + subscriptionConsumer, + null)); + } + + /** + * Subscribe {@link Consumer} to this {@link Flux} that will respectively consume all the + * 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)} + * 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}. + *

+ * 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 + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each value + * @param errorConsumer the consumer to invoke on error signal + * @param completeConsumer the consumer to invoke on complete signal + * @param initialContext the base {@link Context} tied to the subscription that will + * be visible to operators upstream + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe( + @Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer, + @Nullable Context initialContext) { + return subscribeWith(new LambdaSubscriber<>(consumer, errorConsumer, + completeConsumer, + null, + initialContext)); + } + + @Override + @SuppressWarnings("unchecked") + public final void subscribe(Subscriber actual) { + CorePublisher publisher = Operators.onLastAssembly(this); + CoreSubscriber subscriber = Operators.toCoreSubscriber(actual); + + try { + if (publisher instanceof OptimizableOperator) { + OptimizableOperator operator = (OptimizableOperator) publisher; + while (true) { + subscriber = operator.subscribeOrReturn(subscriber); + if (subscriber == null) { + // null means "I will subscribe myself", returning... + return; + } + OptimizableOperator newSource = operator.nextOptimizableSource(); + if (newSource == null) { + publisher = operator.source(); + break; + } + operator = newSource; + } + } + + publisher.subscribe(subscriber); + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(subscriber, e); + return; + } + } + + /** + * An internal {@link Publisher#subscribe(Subscriber)} that will bypass + * {@link Hooks#onLastOperator(Function)} pointcut. + *

+ * In addition to behave as expected by {@link Publisher#subscribe(Subscriber)} + * in a controlled manner, it supports direct subscribe-time {@link Context} passing. + * + * @param actual the {@link Subscriber} interested into the published sequence + * @see Flux#subscribe(Subscriber) + */ + 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)} + * 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. + *

+ * + *

+ * Typically used for slow publisher e.g., blocking IO, fast consumer(s) scenarios. + * + *

+	 * {@code flux.subscribeOn(Schedulers.single()).subscribe() }
+	 * 
+ * + *

+ * Note that {@link Worker#schedule(Runnable)} raising + * {@link java.util.concurrent.RejectedExecutionException} on late + * {@link Subscription#request(long)} will be propagated to the request caller. + * + * @param scheduler a {@link Scheduler} providing the {@link Worker} where to subscribe + * + * @return a {@link Flux} requesting asynchronously + * @see #publishOn(Scheduler) + * @see #subscribeOn(Scheduler, boolean) + */ + public final Flux subscribeOn(Scheduler scheduler) { + return subscribeOn(scheduler, true); + } + + /** + * Run subscribe and onSubscribe on a specified {@link Scheduler}'s {@link Worker}. + * Request will be run on that worker too depending on the {@code requestOnSeparateThread} + * parameter (which defaults to true in the {@link #subscribeOn(Scheduler)} version). + * 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 Flux#create(Consumer, FluxSink.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. + *

+ * + *

+ * Typically used for slow publisher e.g., blocking IO, fast consumer(s) scenarios. + * + *

+	 * {@code flux.subscribeOn(Schedulers.single()).subscribe() }
+	 * 
+ * + *

+ * Note that {@link Worker#schedule(Runnable)} raising + * {@link java.util.concurrent.RejectedExecutionException} on late + * {@link Subscription#request(long)} will be propagated to the request caller. + * + * @param scheduler a {@link Scheduler} providing the {@link Worker} where to subscribe + * @param requestOnSeparateThread whether or not to also perform requests on the worker. + * {@code true} to behave like {@link #subscribeOn(Scheduler)} + * + * @return a {@link Flux} requesting asynchronously + * @see #publishOn(Scheduler) + * @see #subscribeOn(Scheduler) + */ + public final Flux subscribeOn(Scheduler scheduler, boolean requestOnSeparateThread) { + if (this instanceof Callable) { + if (this instanceof Fuseable.ScalarCallable) { + try { + @SuppressWarnings("unchecked") T value = ((Fuseable.ScalarCallable) this).call(); + return onAssembly(new FluxSubscribeOnValue<>(value, scheduler)); + } + catch (Exception e) { + //leave FluxSubscribeOnCallable defer error + } + } + @SuppressWarnings("unchecked") + Callable c = (Callable)this; + return onAssembly(new FluxSubscribeOnCallable<>(c, scheduler)); + } + return onAssembly(new FluxSubscribeOn<>(this, scheduler, requestOnSeparateThread)); + } + + /** + * Subscribe the given {@link Subscriber} to this {@link Flux} and return said + * {@link Subscriber} (eg. a {@link FluxProcessor}). + * + *

+	 * {@code flux.subscribeWith(EmitterProcessor.create()).subscribe() }
+	 * 
+ * + * If you need more control over backpressure and the request, use a {@link BaseSubscriber}. + * + * @param subscriber the {@link Subscriber} to subscribe with and return + * @param the reified type from the input/output subscriber + * + * @return the passed {@link Subscriber} + */ + public final > E subscribeWith(E subscriber) { + subscribe(subscriber); + return subscriber; + } + + /** + * Transform the current {@link Flux} once it emits its first element, making a + * conditional transformation possible. This operator first requests one element + * from the source then applies a transformation derived from the first {@link Signal} + * and the source. The whole source (including the first signal) is passed as second + * argument to the {@link BiFunction} and it is very strongly advised to always build + * upon with operators (see below). + *

+ * Note that the source might complete or error immediately instead of emitting, + * in which case the {@link Signal} would be onComplete or onError. It is NOT + * necessarily an onNext Signal, and must be checked accordingly. + *

+ * For example, this operator could be used to define a dynamic transformation that depends + * on the first element (which could contain routing metadata for instance): + * + *

+	 * {@code
+	 *  fluxOfIntegers.switchOnFirst((signal, flux) -> {
+	 *      if (signal.hasValue()) {
+	 *          ColoredShape firstColor = signal.get();
+	 *          return flux.filter(v -> !v.hasSameColorAs(firstColor))
+	 *      }
+	 *      return flux; //either early complete or error, this forwards the termination in any case
+	 *      //`return flux.onErrorResume(t -> Mono.empty());` instead would suppress an early error
+	 *      //`return Flux.just(1,2,3);` instead would suppress an early error and return 1, 2, 3.
+	 *      //It would also only cancel the original `flux` at the completion of `just`.
+	 *  })
+	 * }
+	 * 
+ *

+ * + *

+ * It is advised to return a {@link Publisher} derived from the original {@link Flux} + * in all cases, as not doing so would keep the original {@link Publisher} open and + * hanging with a single request until the inner {@link Publisher} terminates or + * the whole {@link Flux} is cancelled. + * + * @param transformer A {@link BiFunction} executed once the first signal is + * available and used to transform the source conditionally. The whole source (including + * first signal) is passed as second argument to the BiFunction. + * @param the item type in the returned {@link Flux} + * + * @return a new {@link Flux} that transform the upstream once a signal is available + */ + public final Flux switchOnFirst(BiFunction, Flux, Publisher> transformer) { + return switchOnFirst(transformer, true); + } + + /** + * Transform the current {@link Flux} once it emits its first element, making a + * conditional transformation possible. This operator first requests one element + * from the source then applies a transformation derived from the first {@link Signal} + * and the source. The whole source (including the first signal) is passed as second + * argument to the {@link BiFunction} and it is very strongly advised to always build + * upon with operators (see below). + *

+ * Note that the source might complete or error immediately instead of emitting, + * in which case the {@link Signal} would be onComplete or onError. It is NOT + * necessarily an onNext Signal, and must be checked accordingly. + *

+ * For example, this operator could be used to define a dynamic transformation that depends + * on the first element (which could contain routing metadata for instance): + * + *

+	 * {@code
+	 *  fluxOfIntegers.switchOnFirst((signal, flux) -> {
+	 *      if (signal.hasValue()) {
+	 *          ColoredShape firstColor = signal.get();
+	 *          return flux.filter(v -> !v.hasSameColorAs(firstColor))
+	 *      }
+	 *      return flux; //either early complete or error, this forwards the termination in any case
+	 *      //`return flux.onErrorResume(t -> Mono.empty());` instead would suppress an early error
+	 *      //`return Flux.just(1,2,3);` instead would suppress an early error and return 1, 2, 3.
+	 *      //It would also only cancel the original `flux` at the completion of `just`.
+	 *  })
+	 * }
+	 * 
+ *

+ * + *

+ * It is advised to return a {@link Publisher} derived from the original {@link Flux} + * in all cases, as not doing so would keep the original {@link Publisher} open and + * hanging with a single request. In case the value of the {@code cancelSourceOnComplete} parameter is {@code true} the original publisher until the inner {@link Publisher} terminates or + * the whole {@link Flux} is cancelled. Otherwise the original publisher will hang forever. + * + * @param transformer A {@link BiFunction} executed once the first signal is + * available and used to transform the source conditionally. The whole source (including + * first signal) is passed as second argument to the BiFunction. + * @param the item type in the returned {@link Flux} + * @param cancelSourceOnComplete specify whether original publisher should be cancelled on {@code onComplete} from the derived one + * + * @return a new {@link Flux} that transform the upstream once a signal is available + */ + public final Flux switchOnFirst(BiFunction, Flux, Publisher> transformer, boolean cancelSourceOnComplete) { + return onAssembly(new FluxSwitchOnFirst<>(this, transformer, cancelSourceOnComplete)); + } + + /** + * Switch to an alternative {@link Publisher} if this sequence is completed without any data. + *

+ * + * + * @param alternate the alternative {@link Publisher} if this sequence is empty + * + * @return a new {@link Flux} that falls back on a {@link Publisher} if source is empty + */ + public final Flux switchIfEmpty(Publisher alternate) { + return onAssembly(new FluxSwitchIfEmpty<>(this, alternate)); + } + + /** + * Switch to a new {@link Publisher} generated via a {@link Function} whenever + * this {@link Flux} produces an item. As such, the elements from each generated + * Publisher are emitted in the resulting {@link Flux}. + * + *

+ * + * + * @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); + } + + /** + * Switch to a new {@link Publisher} generated via a {@link Function} whenever + * this {@link Flux} produces an item. As such, the elements from each generated + * Publisher are emitted in the resulting {@link Flux}. + * + *

+ * + * + * @param fn the {@link Function} to generate a {@link Publisher} for each source value + * @param prefetch the produced demand for inner sources + * + * @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 + * + * @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 switchMap(fn), as the default behavior of the + * single-parameter variant will then change to prefetch=0. + */ + @Deprecated + public final Flux switchMap(Function> fn, int prefetch) { + if (prefetch == 0) { + return onAssembly(new FluxSwitchMapNoPrefetch<>(this, fn)); + } + return onAssembly(new FluxSwitchMap<>(this, fn, Queues.unbounded(prefetch), prefetch)); + } + + /** + * 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()}). + *

+ * Note that some monitoring systems like Prometheus require to have the exact same set of + * tags for each meter bearing the same name. + * + * @param key a tag key + * @param value a tag value + * + * @return the same sequence, but bearing tags + * + *@see #name(String) + *@see #metrics() + */ + public final Flux tag(String key, String value) { + return FluxName.createOrAppend(this, key, value); + } + + /** + * 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. + *

+ * + *

+ * 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 + *

+ * 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} + * + * @return a {@link Flux} limited to size N + * @see #take(long, boolean) + */ + public final Flux take(long n) { + return take(n, false); + } + + /** + * Take only the first N values from this {@link Flux}, if available. + *

+ * + *

+ * If {@code limitRequest == true}, ensure that the total amount requested upstream is capped + * 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. + *

+ * 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). + *

+ * takeLimitRequestFalse + *

+ * 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)}). + *

+ * 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 + * (e.g. prefetching operators), consider using {@code limitRequest = true} instead. + * + * @param n the number of items to emit from this {@link Flux} + * @param limitRequest {@code true} to follow the downstream request more closely and limit the upstream request + * to {@code n}. {@code false} to request an unbounded amount from upstream. + * + * @return a {@link Flux} limited to size N + */ + public final Flux take(long n, boolean limitRequest) { + if (limitRequest) { + return onAssembly(new FluxLimitRequest<>(this, n)); + } + if (this instanceof Fuseable) { + return onAssembly(new FluxTakeFuseable<>(this, n)); + } + return onAssembly(new FluxTake<>(this, n)); + } + + /** + * 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). + * + *

+ * + * + * @param timespan the {@link Duration} of the time window during which to emit elements + * from this {@link Flux} + * + * @return a {@link Flux} limited to elements emitted within a specific duration + */ + public final Flux take(Duration timespan) { + return take(timespan, Schedulers.parallel()); + } + + /** + * Relay values from this {@link Flux} until the specified {@link Duration} elapses, + * 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). + * + *

+ * + * + * @param timespan the {@link Duration} of the time window during which to emit elements + * from this {@link Flux} + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a {@link Flux} limited to elements emitted within a specific duration + */ + public final Flux take(Duration timespan, Scheduler timer) { + if (!timespan.isZero()) { + return takeUntilOther(Mono.delay(timespan, timer)); + } + else { + return take(0); + } + } + + /** + * Emit the last N values this {@link Flux} emitted before its completion. + * + *

+ * + * + * @param n the number of items from this {@link Flux} to retain and emit on onComplete + * + * @return a terminating {@link Flux} sub-sequence + * + */ + public final Flux takeLast(int n) { + if(n == 1){ + return onAssembly(new FluxTakeLastOne<>(this)); + } + return onAssembly(new FluxTakeLast<>(this, n)); + } + + /** + * Relay values from this {@link Flux} until the given {@link Predicate} matches. + * This includes the matching data (unlike {@link #takeWhile}). + * + *

+ * + * + * @param predicate the {@link Predicate} that stops the taking of values from this {@link Flux} + * when returning {@literal true}. + * + * @return a new {@link Flux} limited by the predicate + * + */ + public final Flux takeUntil(Predicate predicate) { + return onAssembly(new FluxTakeUntil<>(this, predicate)); + } + + /** + * Relay values from this {@link Flux} until the given {@link Publisher} emits. + * + *

+ * + * + * @param other the companion {@link Publisher} that signals when to stop taking values from this {@link Flux} + * + * @return a new {@link Flux} limited by a companion {@link Publisher} + * + */ + public final Flux takeUntilOther(Publisher other) { + return onAssembly(new FluxTakeUntilOther<>(this, other)); + } + + /** + * Relay values from this {@link Flux} while a predicate returns {@literal TRUE} + * for the values (checked before each value is delivered). + * This only includes the matching data (unlike {@link #takeUntil}). + * + *

+ * + * + * @param continuePredicate the {@link Predicate} invoked each onNext returning {@literal TRUE} + * to relay a value or {@literal FALSE} to terminate + * + * @return a new {@link Flux} taking values from the source while the predicate matches + */ + public final Flux takeWhile(Predicate continuePredicate) { + return onAssembly(new FluxTakeWhile<>(this, continuePredicate)); + } + + /** + * Return a {@code Mono} that completes when this {@link Flux} completes. + * This will actively ignore the sequence and only replay completion or error signals. + *

+ * + *

+ * + *

Discard Support: This operator discards elements from the source. + * + * @return a new {@link Mono} representing the termination of this {@link Flux} + */ + public final Mono then() { + @SuppressWarnings("unchecked") + Mono then = (Mono) new MonoIgnoreElements<>(this); + return Mono.onAssembly(then); + } + + /** + * Let this {@link Flux} complete then play signals from a provided {@link Mono}. + *

+ * In other words ignore element from this {@link Flux} and transform its completion signal into the + * emission and completion signal of a provided {@code Mono}. Error signal is + * replayed in the resulting {@code Mono}. + * + *

+ * + * + *

Discard Support: This operator discards elements from the source. + * + * @param other a {@link Mono} to emit from after termination + * @param the element type of the supplied Mono + * + * @return a new {@link Flux} that wait for source completion then emits from the supplied {@link Mono} + */ + public final Mono then(Mono other) { + return Mono.onAssembly(new MonoIgnoreThen<>(new Publisher[] { this }, other)); + } + + /** + * Return a {@code Mono} that waits for this {@link Flux} to complete then + * for a supplied {@link Publisher Publisher<Void>} to also complete. The + * second completion signal is replayed, or any error signal that occurs instead. + *

+ * + * + *

Discard Support: This operator discards elements from the source. + * + * @param other a {@link Publisher} to wait for after this Flux's termination + * @return a new {@link Mono} completing when both publishers have completed in + * sequence + */ + public final Mono thenEmpty(Publisher other) { + return then(Mono.fromDirect(other)); + } + + /** + * Let this {@link Flux} complete then play another {@link Publisher}. + *

+ * In other words ignore element from this flux and transform the completion signal into a + * {@code Publisher} that will emit elements from the provided {@link Publisher}. + *

+ * + * + *

Discard Support: This operator discards elements from the source. + * + * @param other a {@link Publisher} to emit from after termination + * @param the element type of the supplied Publisher + * + * @return a new {@link Flux} that emits from the supplied {@link Publisher} after + * this Flux completes. + */ + public final Flux thenMany(Publisher other) { + if (this instanceof FluxConcatArray) { + @SuppressWarnings({ "unchecked" }) + FluxConcatArray fluxConcatArray = (FluxConcatArray) this; + return fluxConcatArray.concatAdditionalIgnoredLast(other); + } + + @SuppressWarnings("unchecked") + Flux concat = (Flux)concat(ignoreElements(), other); + return concat; + } + + /** + * Times {@link Subscriber#onNext(Object)} events, encapsulated into a {@link Timed} object + * that lets downstream consumer look at various time information gathered with nanosecond + * resolution using the default clock ({@link Schedulers#parallel()}): + *

    + *
  • {@link Timed#elapsed()}: the time in nanoseconds since last event, as a {@link Duration}. + * For the first onNext, "last event" is the subscription. Otherwise it is the previous onNext. + * This is functionally equivalent to {@link #elapsed()}, with a more expressive and precise + * representation than a {@link Tuple2} with a long.
  • + *
  • {@link Timed#timestamp()}: the timestamp of this onNext, as an {@link java.time.Instant} + * (with nanoseconds part). This is functionally equivalent to {@link #timestamp()}, with a more + * expressive and precise representation than a {@link Tuple2} with a long.
  • + *
  • {@link Timed#elapsedSinceSubscription()}: the time in nanoseconds since subscription, + * as a {@link Duration}.
  • + *
+ *

+ * The {@link Timed} object instances are safe to store and use later, as they are created as an + * immutable wrapper around the {@code } value and immediately passed downstream. + *

+ * + * + * @return a timed {@link Flux} + * @see #elapsed() + * @see #timestamp() + */ + public final Flux> timed() { + return this.timed(Schedulers.parallel()); + } + + /** + * Times {@link Subscriber#onNext(Object)} events, encapsulated into a {@link Timed} object + * that lets downstream consumer look at various time information gathered with nanosecond + * resolution using the provided {@link Scheduler} as a clock: + *

    + *
  • {@link Timed#elapsed()}: the time in nanoseconds since last event, as a {@link Duration}. + * For the first onNext, "last event" is the subscription. Otherwise it is the previous onNext. + * This is functionally equivalent to {@link #elapsed()}, with a more expressive and precise + * representation than a {@link Tuple2} with a long.
  • + *
  • {@link Timed#timestamp()}: the timestamp of this onNext, as an {@link java.time.Instant} + * (with nanoseconds part). This is functionally equivalent to {@link #timestamp()}, with a more + * expressive and precise representation than a {@link Tuple2} with a long.
  • + *
  • {@link Timed#elapsedSinceSubscription()}: the time in nanoseconds since subscription, + * as a {@link Duration}.
  • + *
+ *

+ * The {@link Timed} object instances are safe to store and use later, as they are created as an + * immutable wrapper around the {@code } value and immediately passed downstream. + *

+ * + * + * @return a timed {@link Flux} + * @see #elapsed(Scheduler) + * @see #timestamp(Scheduler) + */ + public final Flux> timed(Scheduler clock) { + return onAssembly(new FluxTimed<>(this, clock)); + } + + /** + * Propagate a {@link TimeoutException} as soon as no item is emitted within the + * given {@link Duration} from the previous emission (or the subscription for the first item). + * + *

+ * + * + * @param timeout the timeout between two signals from this {@link Flux} + * + * @return a {@link Flux} that can time out on a per-item basis + */ + public final Flux timeout(Duration timeout) { + return timeout(timeout, null, Schedulers.parallel()); + } + + /** + * Switch to a fallback {@link Flux} as soon as no item is emitted within the + * given {@link Duration} from the previous emission (or the subscription for the first item). + *

+ * If the given {@link Publisher} is null, signal a {@link TimeoutException} instead. + * + *

+ * + * + * @param timeout the timeout between two signals from this {@link Flux} + * @param fallback the fallback {@link Publisher} to subscribe when a timeout occurs + * + * @return a {@link Flux} that will fallback to a different {@link Publisher} in case of a per-item timeout + */ + public final Flux timeout(Duration timeout, @Nullable Publisher fallback) { + return timeout(timeout, fallback, Schedulers.parallel()); + } + + /** + * Propagate a {@link TimeoutException} as soon as no item is emitted within the + * given {@link Duration} from the previous emission (or the subscription for the first + * item), as measured by the specified {@link Scheduler}. + * + *

+ * + * + * @param timeout the timeout {@link Duration} between two signals from this {@link Flux} + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a {@link Flux} that can time out on a per-item basis + */ + public final Flux timeout(Duration timeout, Scheduler timer) { + return timeout(timeout, null, timer); + } + + /** + * Switch to a fallback {@link Flux} as soon as no item is emitted within the + * given {@link Duration} from the previous emission (or the subscription for the + * first item), as measured on the specified {@link Scheduler}. + *

+ * If the given {@link Publisher} is null, signal a {@link TimeoutException} instead. + * + *

+ * + * + * @param timeout the timeout {@link Duration} between two signals from this {@link Flux} + * @param fallback the fallback {@link Publisher} to subscribe when a timeout occurs + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a {@link Flux} that will fallback to a different {@link Publisher} in case of a per-item timeout + */ + public final Flux timeout(Duration timeout, + @Nullable Publisher fallback, + Scheduler timer) { + final Mono _timer = Mono.delay(timeout, timer).onErrorReturn(0L); + final Function> rest = o -> _timer; + + if(fallback == null) { + return timeout(_timer, rest, timeout.toMillis() + "ms"); + } + return timeout(_timer, rest, fallback); + } + + /** + * Signal a {@link TimeoutException} in case the first item from this {@link Flux} has + * not been emitted before the given {@link Publisher} emits. + * + *

+ * + * + * @param firstTimeout the companion {@link Publisher} that will trigger a timeout if + * emitting before the first signal from this {@link Flux} + * + * @param the type of the timeout Publisher + * + * @return a {@link Flux} that can time out if the first item does not come before + * a signal from a companion {@link Publisher} + * + */ + public final Flux timeout(Publisher firstTimeout) { + return timeout(firstTimeout, t -> never()); + } + + /** + * Signal a {@link TimeoutException} in case the first item from this {@link Flux} has + * not been emitted before the {@code firstTimeout} {@link Publisher} emits, and whenever + * each subsequent elements is not emitted before a {@link Publisher} generated from + * the latest element signals. + * + *

+ * + * + *

Discard Support: This operator discards an element if it comes right after the timeout. + * + * @param firstTimeout the timeout {@link Publisher} that must not emit before the first signal from this {@link Flux} + * @param nextTimeoutFactory the timeout {@link Publisher} factory for each next item + * @param the type of the elements of the first timeout Publisher + * @param the type of the elements of the subsequent timeout Publishers + * + * @return a {@link Flux} that can time out if each element does not come before + * a signal from a per-item companion {@link Publisher} + */ + public final Flux timeout(Publisher firstTimeout, + Function> nextTimeoutFactory) { + return timeout(firstTimeout, nextTimeoutFactory, "first signal from a Publisher"); + } + + private final Flux timeout(Publisher firstTimeout, + Function> nextTimeoutFactory, + String timeoutDescription) { + return onAssembly(new FluxTimeout<>(this, firstTimeout, nextTimeoutFactory, timeoutDescription)); + } + + /** + * Switch to a fallback {@link Publisher} in case the first item from this {@link Flux} has + * not been emitted before the {@code firstTimeout} {@link Publisher} emits, and whenever + * each subsequent elements is not emitted before a {@link Publisher} generated from + * the latest element signals. + * + *

+ * + * + * @param firstTimeout the timeout {@link Publisher} that must not emit before the first signal from this {@link Flux} + * @param nextTimeoutFactory the timeout {@link Publisher} factory for each next item + * @param fallback the fallback {@link Publisher} to subscribe when a timeout occurs + * + * @param the type of the elements of the first timeout Publisher + * @param the type of the elements of the subsequent timeout Publishers + * + * @return a {@link Flux} that can time out if each element does not come before + * a signal from a per-item companion {@link Publisher} + */ + public final Flux timeout(Publisher firstTimeout, + Function> nextTimeoutFactory, Publisher + fallback) { + return onAssembly(new FluxTimeout<>(this, firstTimeout, nextTimeoutFactory, + fallback)); + } + + /** + * Emit a {@link reactor.util.function.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}. + * + *

+ * + * + * @return a timestamped {@link Flux} + * @see #timed() + * @see Timed#timestamp() + */ + public final Flux> timestamp() { + return timestamp(Schedulers.parallel()); + } + + /** + * Emit a {@link reactor.util.function.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}. + * + *

The provider {@link Scheduler} will be asked to {@link Scheduler#now(TimeUnit) provide time} + * with a granularity of {@link TimeUnit#MILLISECONDS}. In order for this operator to work as advertised, the + * provided Scheduler should thus return results that can be interpreted as unix timestamps.

+ *

+ * + * + * + * @param scheduler the {@link Scheduler} to read time from + * @return a timestamped {@link Flux} + * @see Scheduler#now(TimeUnit) + * @see #timed(Scheduler) + * @see Timed#timestamp() + */ + public final Flux> timestamp(Scheduler scheduler) { + Objects.requireNonNull(scheduler, "scheduler"); + return map(d -> Tuples.of(scheduler.now(TimeUnit.MILLISECONDS), d)); + } + + /** + * Transform this {@link Flux} into a lazy {@link Iterable} blocking on + * {@link Iterator#next()} calls. + * + *

+ * + *

+ * Note that iterating from within threads marked as "non-blocking only" is illegal and will + * cause an {@link IllegalStateException} to be thrown, but obtaining the {@link Iterable} + * itself within these threads is ok. + * + * @return a blocking {@link Iterable} + */ + public final Iterable toIterable() { + return toIterable(Queues.SMALL_BUFFER_SIZE); + } + + /** + * Transform this {@link Flux} into a lazy {@link Iterable} blocking on + * {@link Iterator#next()} calls. + *

+ * + *

+ * Note that iterating from within threads marked as "non-blocking only" is illegal and will + * cause an {@link IllegalStateException} to be thrown, but obtaining the {@link Iterable} + * itself within these threads is ok. + * + * @param batchSize the bounded capacity to prefetch from this {@link Flux} or + * {@code Integer.MAX_VALUE} for unbounded demand + * @return a blocking {@link Iterable} + */ + public final Iterable toIterable(int batchSize) { + return toIterable(batchSize, null); + } + + /** + * Transform this {@link Flux} into a lazy {@link Iterable} blocking on + * {@link Iterator#next()} calls. + * + *

+ * + *

+ * Note that iterating from within threads marked as "non-blocking only" is illegal and will + * cause an {@link IllegalStateException} to be thrown, but obtaining the {@link Iterable} + * itself within these threads is ok. + * + * @param batchSize the bounded capacity to prefetch from this {@link Flux} or + * {@code Integer.MAX_VALUE} for unbounded demand + * @param queueProvider the supplier of the queue implementation to be used for storing + * elements emitted faster than the iteration + * + * @return a blocking {@link Iterable} + */ + public final Iterable toIterable(int batchSize, @Nullable Supplier> + queueProvider) { + final Supplier> provider; + if(queueProvider == null){ + provider = Queues.get(batchSize); + } + else{ + provider = () -> Hooks.wrapQueue(queueProvider.get()); + } + return new BlockingIterable<>(this, batchSize, provider); + } + + /** + * Transform this {@link Flux} into a lazy {@link Stream} blocking for each source + * {@link Subscriber#onNext(Object) onNext} call. + * + *

+ * + *

+ * Note that iterating from within threads marked as "non-blocking only" is illegal and will + * cause an {@link IllegalStateException} to be thrown, but obtaining the {@link Stream} + * itself or applying lazy intermediate operation on the stream within these threads is ok. + * + * @return a {@link Stream} of unknown size with onClose attached to {@link Subscription#cancel()} + */ + public final Stream toStream() { + return toStream(Queues.SMALL_BUFFER_SIZE); + } + + /** + * Transform this {@link Flux} into a lazy {@link Stream} blocking for each source + * {@link Subscriber#onNext(Object) onNext} call. + *

+ * + *

+ * Note that iterating from within threads marked as "non-blocking only" is illegal and will + * cause an {@link IllegalStateException} to be thrown, but obtaining the {@link Stream} + * itself or applying lazy intermediate operation on the stream within these threads is ok. + * + * + * @param batchSize the bounded capacity to prefetch from this {@link Flux} or + * {@code Integer.MAX_VALUE} for unbounded demand + * @return a {@link Stream} of unknown size with onClose attached to {@link Subscription#cancel()} + */ + public final Stream toStream(int batchSize) { + final Supplier> provider; + provider = Queues.get(batchSize); + return new BlockingIterable<>(this, batchSize, provider).stream(); + } + + /** + * Transform this {@link Flux} in order to generate a target {@link Flux}. Unlike {@link #transformDeferred(Function)}, the + * provided function is executed as part of assembly. + *

+	 * Function applySchedulers = flux -> flux.subscribeOn(Schedulers.boundedElastic())
+	 *                                                    .publishOn(Schedulers.parallel());
+	 * flux.transform(applySchedulers).map(v -> v * v).subscribe();
+	 * 
+ *

+ * + * + * @param transformer the {@link Function} to immediately map this {@link Flux} into a target {@link Flux} + * instance. + * @param the item type in the returned {@link Flux} + * + * @return a new {@link Flux} + * @see #transformDeferred(Function) transformDeferred(Function) for deferred composition of Flux for each @link Subscriber + * @see #as(Function) as(Function) for a loose conversion to an arbitrary type + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public final Flux transform(Function, ? extends Publisher> transformer) { + if (Hooks.DETECT_CONTEXT_LOSS) { + transformer = new ContextTrackingFunctionWrapper(transformer); + } + return onAssembly(from(transformer.apply(this))); + } + + /** + * Defer the transformation of this {@link Flux} in order to generate a target {@link Flux} type. + * A transformation will occur for each {@link Subscriber}. For instance: + *

+	 * flux.transformDeferred(original -> original.log());
+	 * 
+ *

+ * + * + * @param transformer the {@link Function} to lazily map this {@link Flux} into a target {@link Publisher} + * instance for each new subscriber + * @param the item type in the returned {@link Publisher} + * + * @return a new {@link Flux} + * @see #transform(Function) transform(Function) for immmediate transformation of Flux + * @see #transformDeferredContextual(BiFunction) transformDeferredContextual(BiFunction) for a similarly deferred transformation of Flux reading the ContextView + * @see #as(Function) as(Function) for a loose conversion to an arbitrary type + */ + public final Flux transformDeferred(Function, ? extends Publisher> transformer) { + return defer(() -> { + if (Hooks.DETECT_CONTEXT_LOSS) { + @SuppressWarnings({"rawtypes", "unchecked"}) + ContextTrackingFunctionWrapper wrapper = new ContextTrackingFunctionWrapper((Function) transformer); + return wrapper.apply(this); + } + return transformer.apply(this); + }); + } + + /** + * Defer the given transformation to this {@link Flux} in order to generate a + * target {@link Flux} type. A transformation will occur for each + * {@link Subscriber}. In addition, the transforming {@link BiFunction} exposes + * the {@link ContextView} of each {@link Subscriber}. For instance: + * + *

+	 * Flux<T> fluxLogged = flux.transformDeferredContextual((original, ctx) -> original.log("for RequestID" + ctx.get("RequestID"))
+	 * //...later subscribe. Each subscriber has its Context with a RequestID entry
+	 * fluxLogged.contextWrite(Context.of("RequestID", "requestA").subscribe();
+	 * fluxLogged.contextWrite(Context.of("RequestID", "requestB").subscribe();
+	 * 
+ *

+ * + * + * @param transformer the {@link BiFunction} to lazily map this {@link Flux} into a target {@link Flux} + * instance upon subscription, with access to {@link ContextView} + * @param the item type in the returned {@link Publisher} + * @return a new {@link Flux} + * @see #transform(Function) transform(Function) for immmediate transformation of Flux + * @see #transformDeferred(Function) transformDeferred(Function) for a similarly deferred transformation of Flux without the ContextView + * @see #as(Function) as(Function) for a loose conversion to an arbitrary type + */ + public final Flux transformDeferredContextual(BiFunction, ? super ContextView, ? extends Publisher> transformer) { + return deferContextual(ctxView -> { + if (Hooks.DETECT_CONTEXT_LOSS) { + ContextTrackingFunctionWrapper wrapper = new ContextTrackingFunctionWrapper<>( + publisher -> transformer.apply(wrap(publisher), ctxView), + transformer.toString() + ); + + return wrapper.apply(this); + } + return transformer.apply(this, ctxView); + }); + } + + /** + * 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 after {@code maxSize} items have been routed. + * + *

+ * + * + *

+ * 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. + * + *

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 + * + * @return a {@link Flux} of {@link Flux} windows based on element count + */ + public final Flux> window(int maxSize) { + return onAssembly(new FluxWindow<>(this, maxSize, Queues.get(maxSize))); + } + + /** + * Split this {@link Flux} sequence into multiple {@link Flux} windows of size + * {@code maxSize}, that each open every {@code skip} elements in the source. + * + *

+ * When maxSize < skip : dropping windows + *

+ * + *

+ * When maxSize > skip : overlapping windows + *

+ * + *

+ * When maxSize == skip : exact windows + *

+ * + * + *

+ * 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. + * + *

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. + * + * @param maxSize the maximum number of items to emit in the window before closing it + * @param skip the number of items to count before opening and emitting a new window + * + * @return a {@link Flux} of {@link Flux} windows based on element count and opened every skipCount + */ + public final Flux> window(int maxSize, int skip) { + return onAssembly(new FluxWindow<>(this, + maxSize, + skip, + Queues.unbounded(Queues.XS_BUFFER_SIZE), + Queues.unbounded(Queues.XS_BUFFER_SIZE))); + } + + /** + * Split this {@link Flux} sequence into continuous, non-overlapping windows + * where the window boundary is signalled by another {@link Publisher} + * + *

+ * + * + *

+ * 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. + * + *

Discard Support: This operator discards elements it internally queued for backpressure + * upon cancellation or error triggered by a data signal. + * + * @param boundary a {@link Publisher} to emit any item for a split signal and complete to terminate + * + * @return a {@link Flux} of {@link Flux} windows delimited by a given {@link Publisher} + */ + public final Flux> window(Publisher boundary) { + return onAssembly(new FluxWindowBoundary<>(this, + boundary, Queues.unbounded(Queues.XS_BUFFER_SIZE))); + } + + /** + * Split this {@link Flux} sequence into continuous, non-overlapping windows that open + * for a {@code windowingTimespan} {@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. + * + *

Discard Support: This operator discards elements it internally queued for backpressure + * upon cancellation or error triggered by a data signal. + * + * @param windowingTimespan the {@link Duration} to delimit {@link Flux} windows + * + * @return a {@link Flux} of {@link Flux} windows continuously opened for a given {@link Duration} + */ + public final Flux> window(Duration windowingTimespan) { + return window(windowingTimespan, Schedulers.parallel()); + } + + /** + * Split this {@link Flux} sequence into multiple {@link Flux} windows that open + * for a given {@code windowingTimespan} {@link Duration}, after which it closes with onComplete. + * Each window is opened at a regular {@code timeShift} interval, starting from the + * first item. + * Both durations are measured on the {@link Schedulers#parallel() parallel} Scheduler. + * + *

+ * When windowingTimespan < openWindowEvery : dropping windows + *

+ * + *

+ * When windowingTimespan > openWindowEvery : overlapping windows + *

+ * + *

+ * When windowingTimespan == openWindowEvery : exact windows + *

+ * + * + *

+ * 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. + * + *

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. + * + * @param windowingTimespan the maximum {@link Flux} window {@link Duration} + * @param openWindowEvery the period of time at which to create new {@link Flux} windows + * + * @return a {@link Flux} of {@link Flux} windows opened at regular intervals and + * closed after a {@link Duration} + * + */ + public final Flux> window(Duration windowingTimespan, Duration openWindowEvery) { + return window(windowingTimespan, openWindowEvery, Schedulers.parallel()); + } + + /** + * Split this {@link Flux} sequence into continuous, non-overlapping windows that open + * for a {@code windowingTimespan} {@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. + * + *

Discard Support: This operator discards elements it internally queued for backpressure + * upon cancellation or error triggered by a data signal. + * + * @param windowingTimespan the {@link Duration} to delimit {@link Flux} windows + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a {@link Flux} of {@link Flux} windows continuously opened for a given {@link Duration} + */ + public final Flux> window(Duration windowingTimespan, Scheduler timer) { + return window(interval(windowingTimespan, timer)); + } + + /** + * Split this {@link Flux} sequence into multiple {@link Flux} windows that open + * for a given {@code windowingTimespan} {@link Duration}, after which it closes with onComplete. + * Each window is opened at a regular {@code timeShift} interval, starting from the + * first item. + * Both durations are measured on the provided {@link Scheduler}. + * + *

+ * When windowingTimespan < openWindowEvery : dropping windows + *

+ * + *

+ * When windowingTimespan > openWindowEvery : overlapping windows + *

+ * + *

+ * When openWindowEvery == openWindowEvery : exact windows + *

+ * + * + *

+ * 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. + * + *

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. + * + * @param windowingTimespan the maximum {@link Flux} window {@link Duration} + * @param openWindowEvery the period of time at which to create new {@link Flux} windows + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a {@link Flux} of {@link Flux} windows opened at regular intervals and + * closed after a {@link Duration} + */ + public final Flux> window(Duration windowingTimespan, Duration openWindowEvery, Scheduler timer) { + if (openWindowEvery.equals(windowingTimespan)) { + return window(windowingTimespan); + } + return windowWhen(interval(Duration.ZERO, openWindowEvery, timer), aLong -> Mono.delay(windowingTimespan, timer)); + } + + /** + * 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. + * + *

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 + * + * @return a {@link Flux} of {@link Flux} windows based on element count and duration + */ + public final Flux> windowTimeout(int maxSize, Duration maxTime) { + return windowTimeout(maxSize, maxTime , Schedulers.parallel()); + } + + /** + * 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. + * + *

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 + * + * @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)); + } + + /** + * 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. + *

+ * Windows are lazily made available downstream at the point where they receive their + * first event (an element is pushed, the window errors). This variant shouldn't + * expose empty windows, as the separators are emitted into + * the windows they close. + * + *

+ * + * + *

+ * 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. + * + *

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. + * + * @param boundaryTrigger a predicate that triggers the next window when it becomes true. + * @return a {@link Flux} of {@link Flux} windows, bounded depending + * on the predicate. + */ + public final Flux> windowUntil(Predicate boundaryTrigger) { + return windowUntil(boundaryTrigger, false); + } + + /** + * 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. + *

+ * Windows are lazily made available downstream at the point where they receive their + * first event (an element is pushed, the window completes or errors). + *

+ * If {@code cutBefore} is true, the old window will onComplete and the triggering + * element will be emitted in the new window, which becomes immediately available. + * This variant can emit an empty window if the sequence starts with a separator. + *

+ * Otherwise, the triggering element will be emitted in the old window before it does + * onComplete, similar to {@link #windowUntil(Predicate)}. This variant shouldn't + * expose empty windows, as the separators are emitted into the windows they close. + *

+ * + * + *

+ * 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. + * + *

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. + * + * @param boundaryTrigger a predicate that triggers the next window when it becomes true. + * @param cutBefore set to true to include the triggering element in the new window rather than the old. + * @return a {@link Flux} of {@link Flux} windows, bounded depending + * on the predicate. + */ + public final Flux> windowUntil(Predicate boundaryTrigger, boolean cutBefore) { + return windowUntil(boundaryTrigger, cutBefore, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Split this {@link Flux} sequence into multiple {@link Flux} windows delimited by the given + * predicate and using a prefetch. A new window is opened each time the predicate + * returns true. + *

+ * Windows are lazily made available downstream at the point where they receive their + * first event (an element is pushed, the window completes or errors). + *

+ * If {@code cutBefore} is true, the old window will onComplete and the triggering + * element will be emitted in the new window. This variant can emit an empty window + * if the sequence starts with a separator. + *

+ * Otherwise, the triggering element will be emitted in the old window before it does + * onComplete, similar to {@link #windowUntil(Predicate)}. This variant shouldn't + * expose empty windows, as the separators are emitted into the windows they close. + *

+ * + * + *

+ * 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. + * + *

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. + * + * @param boundaryTrigger a predicate that triggers the next window when it becomes true. + * @param cutBefore set to true to include the triggering element in the new window rather than the old. + * @param prefetch the request size to use for this {@link Flux}. + * @return a {@link Flux} of {@link Flux} windows, bounded depending + * on the predicate. + */ + public final Flux> windowUntil(Predicate boundaryTrigger, boolean cutBefore, int prefetch) { + return onAssembly(new FluxWindowPredicate<>(this, + Queues.unbounded(prefetch), + Queues.unbounded(prefetch), + prefetch, + boundaryTrigger, + cutBefore ? FluxBufferPredicate.Mode.UNTIL_CUT_BEFORE : FluxBufferPredicate.Mode.UNTIL)); + } + + /** + * Collect subsequent repetitions of an element (that is, if they arrive right after + * one another) into multiple {@link Flux} windows. + * + *

+ * + * + *

+ * 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. + * + *

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() { + return windowUntilChanged(identityFunction()); + } + + /** + * Collect subsequent repetitions of an element (that is, if they arrive right after + * one another), as compared by a key extracted through the user provided {@link + * Function}, into multiple {@link Flux} windows. + * + *

+ * + * + *

+ * 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. + * + *

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. + * + * @param keySelector function to compute comparison key for each element + * @return a microbatched {@link Flux} of {@link Flux} windows. + */ + public final Flux> windowUntilChanged(Function keySelector) { + return windowUntilChanged(keySelector, equalPredicate()); + } + + /** + * Collect subsequent repetitions of an element (that is, if they arrive right after + * one another), as compared by a key extracted through the user provided {@link + * Function} and compared using a supplied {@link BiPredicate}, into multiple + * {@link Flux} windows. + * + *

+ * + * + *

+ * 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. + * + *

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. + * + * @param keySelector function to compute comparison key for each element + * @param keyComparator predicate used to compare keys + * @return a microbatched {@link Flux} of {@link Flux} windows. + */ + public final Flux> windowUntilChanged(Function keySelector, + BiPredicate keyComparator) { + return Flux.defer(() -> windowUntil(new FluxBufferPredicate.ChangedPredicate + (keySelector, keyComparator), true)); + } + + /** + * Split this {@link Flux} sequence into multiple {@link Flux} windows that stay open + * while a given predicate matches the source elements. Once the predicate returns + * false, the window closes with an onComplete and the triggering element is discarded. + *

+ * Windows are lazily made available downstream at the point where they receive their + * first event (an element is pushed, the window completes or errors). Empty windows + * can happen when a sequence starts with a separator or contains multiple separators, + * but a sequence that finishes with a separator won't cause a remainder empty window + * to be emitted. + *

+ * + * + *

+ * 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. + * + *

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 + * that were bound for it until the main sequence completes or creation of a new window is triggered. + * + * @param inclusionPredicate a predicate that triggers the next window when it becomes false. + * @return a {@link Flux} of {@link Flux} windows, each containing + * subsequent elements that all passed a predicate. + */ + public final Flux> windowWhile(Predicate inclusionPredicate) { + return windowWhile(inclusionPredicate, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Split this {@link Flux} sequence into multiple {@link Flux} windows that stay open + * while a given predicate matches the source elements. Once the predicate returns + * false, the window closes with an onComplete and the triggering element is discarded. + *

+ * Windows are lazily made available downstream at the point where they receive their + * first event (an element is pushed, the window completes or errors). Empty windows + * can happen when a sequence starts with a separator or contains multiple separators, + * but a sequence that finishes with a separator won't cause a remainder empty window + * to be emitted. + *

+ * + * + *

+ * 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. + * + *

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 + * that were bound for it until the main sequence completes or creation of a new window is triggered. + * + * @param inclusionPredicate a predicate that triggers the next window when it becomes false. + * @param prefetch the request size to use for this {@link Flux}. + * @return a {@link Flux} of {@link Flux} windows, each containing + * subsequent elements that all passed a predicate. + */ + public final Flux> windowWhile(Predicate inclusionPredicate, int prefetch) { + return onAssembly(new FluxWindowPredicate<>(this, + Queues.unbounded(prefetch), + Queues.unbounded(prefetch), + prefetch, + inclusionPredicate, + FluxBufferPredicate.Mode.WHILE)); + } + + /** + * Split this {@link Flux} sequence into potentially overlapping windows controlled by items of a + * start {@link Publisher} and end {@link Publisher} derived from the start values. + * + *

+ * When Open signal is strictly not overlapping Close signal : dropping windows + *

+ * When Open signal is strictly more frequent than Close signal : overlapping windows + *

+ * When Open signal is exactly coordinated with Close signal : exact windows + *

+ * + * + *

+ * 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. + * + *

Discard Support: This operator DOES NOT discard elements. + * + * @param bucketOpening a {@link Publisher} that opens a new window when it emits any item + * @param closeSelector a {@link Function} given an opening signal and returning a {@link Publisher} that + * will close the window when emitting + * + * @param the type of the sequence opening windows + * @param the type of the sequence closing windows opened by the bucketOpening Publisher's elements + * + * @return a {@link Flux} of {@link Flux} windows opened by signals from a first + * {@link Publisher} and lasting until a selected second {@link Publisher} emits + */ + public final Flux> windowWhen(Publisher bucketOpening, + final Function> closeSelector) { + return onAssembly(new FluxWindowWhen<>(this, + bucketOpening, + closeSelector, + Queues.unbounded(Queues.XS_BUFFER_SIZE))); + } + + /** + * Combine the most recently emitted values from both this {@link Flux} and another + * {@link Publisher} through a {@link BiFunction} and emits the result. + *

+ * The operator will drop values from this {@link Flux} until the other + * {@link Publisher} produces any value. + *

+ * If the other {@link Publisher} completes without any value, the sequence is completed. + * + *

+ * + * + * @param other the {@link Publisher} to combine with + * @param resultSelector the bi-function called with each pair of source and other + * elements that should return a single value to be emitted + * + * @param the other {@link Publisher} sequence type + * @param the result type + * + * @return a combined {@link Flux} gated by another {@link Publisher} + */ + public final Flux withLatestFrom(Publisher other, BiFunction resultSelector){ + return onAssembly(new FluxWithLatestFrom<>(this, other, resultSelector)); + } + + /** + * Zip this {@link Flux} with another {@link Publisher} source, that is to say wait + * for both to emit one element and combine these elements once into a {@link Tuple2}. + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source2 The second source {@link Publisher} to zip with this {@link Flux}. + * @param type of the value from source2 + * + * @return a zipped {@link Flux} + * + */ + public final Flux> zipWith(Publisher source2) { + return zipWith(source2, tuple2Function()); + } + + /** + * Zip this {@link Flux} with another {@link Publisher} source, that is to say wait + * for both to emit one element and combine these elements using a {@code combinator} + * {@link BiFunction} + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source2 The second source {@link Publisher} to zip with this {@link Flux}. + * @param combinator The aggregate function that will receive a unique value from each + * source and return the value to signal downstream + * @param type of the value from source2 + * @param The produced output after transformation by the combinator + * + * @return a zipped {@link Flux} + */ + 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) { + return result; + } + } + return zip(this, source2, combinator); + } + + /** + * Zip this {@link Flux} with another {@link Publisher} source, that is to say wait + * for both to emit one element and combine these elements using a {@code combinator} + * {@link BiFunction} + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + *

+ * + * + * @param source2 The second source {@link Publisher} to zip with this {@link Flux}. + * @param prefetch the request size to use for this {@link Flux} and the other {@link Publisher} + * @param combinator The aggregate function that will receive a unique value from each + * source and return the value to signal downstream + * @param type of the value from source2 + * @param The produced output after transformation by the combinator + * + * @return a zipped {@link Flux} + */ + @SuppressWarnings("unchecked") + public final Flux zipWith(Publisher source2, + int prefetch, + BiFunction combinator) { + return zip(objects -> combinator.apply((T) objects[0], (T2) objects[1]), + prefetch, + this, + source2); + } + + /** + * Zip this {@link Flux} with another {@link Publisher} source, that is to say wait + * for both to emit one element and combine these elements once into a {@link Tuple2}. + * The operator will continue doing so until any of the sources completes. + * Errors will immediately be forwarded. + * This "Step-Merge" processing is especially useful in Scatter-Gather scenarios. + * + *

+ * + * + * @param source2 The second source {@link Publisher} to zip with this {@link Flux}. + * @param prefetch the request size to use for this {@link Flux} and the other {@link Publisher} + * @param type of the value from source2 + * + * @return a zipped {@link Flux} + */ + public final Flux> zipWith(Publisher source2, int prefetch) { + return zipWith(source2, prefetch, tuple2Function()); + } + + /** + * Zip elements from this {@link Flux} with the content of an {@link Iterable}, that is + * to say combine one element from each, pairwise, into a {@link Tuple2}. + * + *

+ * + * + * @param iterable the {@link Iterable} to zip with + * @param the value type of the other iterable sequence + * + * @return a zipped {@link Flux} + * + */ + @SuppressWarnings("unchecked") + public final Flux> zipWithIterable(Iterable iterable) { + return zipWithIterable(iterable, tuple2Function()); + } + + /** + * Zip elements from this {@link Flux} with the content of an {@link Iterable}, that is + * to say combine one element from each, pairwise, using the given zipper {@link BiFunction}. + * + * + *

+ * + * + * @param iterable the {@link Iterable} to zip with + * @param zipper the {@link BiFunction} pair combinator + * + * @param the value type of the other iterable sequence + * @param the result type + * + * @return a zipped {@link Flux} + * + */ + public final Flux zipWithIterable(Iterable iterable, + BiFunction zipper) { + return onAssembly(new FluxZipIterable<>(this, iterable, zipper)); + } + + /** + * To be used by custom operators: invokes assembly {@link Hooks} pointcut given a + * {@link Flux}, potentially returning a new {@link Flux}. This is for example useful + * to activate cross-cutting concerns at assembly time, eg. a generalized + * {@link #checkpoint()}. + * + * @param the value type + * @param source the source to apply assembly hooks onto + * + * @return the source, potentially wrapped with assembly time cross-cutting behavior + */ + @SuppressWarnings("unchecked") + protected static Flux onAssembly(Flux source) { + Function hook = Hooks.onEachOperatorHook; + if(hook != null) { + source = (Flux) hook.apply(source); + } + if (Hooks.GLOBAL_TRACE) { + AssemblySnapshot stacktrace = new AssemblySnapshot(null, Traces.callSiteSupplierFactory.get()); + source = (Flux) Hooks.addAssemblyInfo(source, stacktrace); + } + return source; + } + + /** + * To be used by custom operators: invokes assembly {@link Hooks} pointcut given a + * {@link ConnectableFlux}, potentially returning a new {@link ConnectableFlux}. This + * is for example useful to activate cross-cutting concerns at assembly time, eg. a + * generalized {@link #checkpoint()}. + * + * @param the value type + * @param source the source to apply assembly hooks onto + * + * @return the source, potentially wrapped with assembly time cross-cutting behavior + */ + @SuppressWarnings("unchecked") + protected static ConnectableFlux onAssembly(ConnectableFlux source) { + Function hook = Hooks.onEachOperatorHook; + if(hook != null) { + source = (ConnectableFlux) hook.apply(source); + } + if (Hooks.GLOBAL_TRACE) { + AssemblySnapshot stacktrace = new AssemblySnapshot(null, Traces.callSiteSupplierFactory.get()); + source = (ConnectableFlux) Hooks.addAssemblyInfo(source, stacktrace); + } + return source; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + + final Flux flatMap(Function> mapper, boolean delayError, int concurrency, int prefetch) { + return onAssembly(new FluxFlatMap<>( + this, + mapper, + delayError, + concurrency, + Queues.get(concurrency), + prefetch, + Queues.get(prefetch) + )); + } + + final Flux flatMapSequential(Function> mapper, boolean delayError, int maxConcurrency, + int prefetch) { + return onAssembly(new FluxMergeSequential<>(this, mapper, maxConcurrency, + prefetch, delayError ? FluxConcatMap.ErrorMode.END : + FluxConcatMap.ErrorMode.IMMEDIATE)); + } + + @SuppressWarnings("unchecked") + static Flux doOnSignal(Flux source, + @Nullable Consumer onSubscribe, + @Nullable Consumer onNext, + @Nullable Consumer onError, + @Nullable Runnable onComplete, + @Nullable Runnable onAfterTerminate, + @Nullable LongConsumer onRequest, + @Nullable Runnable onCancel) { + if (source instanceof Fuseable) { + return onAssembly(new FluxPeekFuseable<>(source, + onSubscribe, + onNext, + onError, + onComplete, + onAfterTerminate, + onRequest, + onCancel)); + } + return onAssembly(new FluxPeek<>(source, + onSubscribe, + onNext, + onError, + onComplete, + onAfterTerminate, + onRequest, + onCancel)); + } + + /** + * Returns the appropriate Mono instance for a known Supplier Flux, WITHOUT applying hooks + * (see {@link #wrap(Publisher)}). + * + * @param supplier the supplier Flux + * + * @return the mono representing that Flux + */ + static Mono wrapToMono(Callable supplier) { + if (supplier instanceof Fuseable.ScalarCallable) { + Fuseable.ScalarCallable scalarCallable = (Fuseable.ScalarCallable) supplier; + + T v; + try { + v = scalarCallable.call(); + } + catch (Exception e) { + return new MonoError<>(Exceptions.unwrap(e)); + } + if (v == null) { + return MonoEmpty.instance(); + } + return new MonoJust<>(v); + } + return new MonoCallable<>(supplier); + } + + @SafeVarargs + static Flux merge(int prefetch, boolean delayError, Publisher... sources) { + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + return from(sources[0]); + } + return onAssembly(new FluxMerge<>(sources, + delayError, + sources.length, + Queues.get(sources.length), + prefetch, + Queues.get(prefetch))); + } + + @SafeVarargs + static Flux mergeSequential(int prefetch, boolean delayError, + Publisher... sources) { + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + return from(sources[0]); + } + return onAssembly(new FluxMergeSequential<>(new FluxArray<>(sources), + identityFunction(), sources.length, prefetch, + delayError ? FluxConcatMap.ErrorMode.END : FluxConcatMap.ErrorMode.IMMEDIATE)); + } + + static Flux mergeSequential(Publisher> sources, + boolean delayError, int maxConcurrency, int prefetch) { + return onAssembly(new FluxMergeSequential<>(from(sources), + identityFunction(), + maxConcurrency, prefetch, delayError ? FluxConcatMap.ErrorMode.END : + FluxConcatMap.ErrorMode.IMMEDIATE)); + } + + static Flux mergeSequential(Iterable> sources, + boolean delayError, int maxConcurrency, int prefetch) { + return onAssembly(new FluxMergeSequential<>(new FluxIterable<>(sources), + identityFunction(), maxConcurrency, prefetch, + delayError ? FluxConcatMap.ErrorMode.END : FluxConcatMap.ErrorMode.IMMEDIATE)); + } + + + static BooleanSupplier countingBooleanSupplier(BooleanSupplier predicate, long max) { + if (max <= 0) { + return predicate; + } + return new BooleanSupplier() { + long n; + + @Override + public boolean getAsBoolean() { + return n++ < max && predicate.getAsBoolean(); + } + }; + } + + static Predicate countingPredicate(Predicate predicate, long max) { + if (max == 0) { + return predicate; + } + return new Predicate() { + long n; + + @Override + public boolean test(O o) { + return n++ < max && predicate.test(o); + } + }; + } + + @SuppressWarnings("unchecked") + static Supplier> hashSetSupplier() { + return SET_SUPPLIER; + } + + @SuppressWarnings("unchecked") + static Supplier> listSupplier() { + return LIST_SUPPLIER; + } + + @SuppressWarnings("unchecked") + static BiPredicate equalPredicate() { + return OBJECT_EQUAL; + } + + @SuppressWarnings("unchecked") + static Function identityFunction(){ + return IDENTITY_FUNCTION; + } + + @SuppressWarnings("unchecked") + static BiFunction> tuple2Function() { + return TUPLE2_BIFUNCTION; + } + + /** + * Unchecked wrap of {@link Publisher} as {@link Flux}, supporting {@link Fuseable} sources. + * Note that this bypasses {@link Hooks#onEachOperator(String, Function) assembly hooks}. + * + * @param source the {@link Publisher} to wrap + * @param input upstream type + * @return a wrapped {@link Flux} + */ + @SuppressWarnings("unchecked") + static Flux wrap(Publisher source) { + if (source instanceof Flux) { + return (Flux) source; + } + + //for scalars we'll instantiate the operators directly to avoid onAssembly + if (source instanceof Fuseable.ScalarCallable) { + try { + @SuppressWarnings("unchecked") I t = + ((Fuseable.ScalarCallable) source).call(); + if (t != null) { + return new FluxJust<>(t); + } + return FluxEmpty.instance(); + } + catch (Exception e) { + return new FluxError<>(Exceptions.unwrap(e)); + } + } + + if(source instanceof Mono){ + if(source instanceof Fuseable){ + return new FluxSourceMonoFuseable<>((Mono)source); + } + return new FluxSourceMono<>((Mono)source); + } + if(source instanceof Fuseable){ + return new FluxSourceFuseable<>(source); + } + return new FluxSource<>(source); + } + + @SuppressWarnings("rawtypes") + static final BiFunction TUPLE2_BIFUNCTION = Tuples::of; + @SuppressWarnings("rawtypes") + static final Supplier LIST_SUPPLIER = ArrayList::new; + @SuppressWarnings("rawtypes") + static final Supplier SET_SUPPLIER = HashSet::new; + static final BooleanSupplier ALWAYS_BOOLEAN_SUPPLIER = () -> true; + static final BiPredicate OBJECT_EQUAL = Object::equals; + @SuppressWarnings("rawtypes") + static final Function IDENTITY_FUNCTION = Function.identity(); + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxArray.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxArray.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxArray.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Emits the contents of a wrapped (shared) array. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxArray extends Flux implements Fuseable, SourceProducer { + + final T[] array; + + @SafeVarargs + public FluxArray(T... array) { + this.array = Objects.requireNonNull(array, "array"); + } + + @SuppressWarnings("unchecked") + public static void subscribe(CoreSubscriber s, T[] array) { + if (array.length == 0) { + Operators.complete(s); + return; + } + if (s instanceof ConditionalSubscriber) { + s.onSubscribe(new ArrayConditionalSubscription<>((ConditionalSubscriber) s, array)); + } + else { + s.onSubscribe(new ArraySubscription<>(s, array)); + } + } + + @Override + public void subscribe(CoreSubscriber actual) { + subscribe(actual, array); + } + + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.BUFFERED) return array.length; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + static final class ArraySubscription + implements InnerProducer, SynchronousSubscription { + + final CoreSubscriber actual; + + final T[] array; + + int index; + + volatile boolean cancelled; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ArraySubscription.class, "requested"); + + ArraySubscription(CoreSubscriber actual, T[] array) { + this.actual = actual; + this.array = array; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (Operators.addCap(REQUESTED, this, n) == 0) { + if (n == Long.MAX_VALUE) { + fastPath(); + } + else { + slowPath(n); + } + } + } + } + + void slowPath(long n) { + final T[] a = array; + final int len = a.length; + final Subscriber s = actual; + + int i = index; + int e = 0; + + for (; ; ) { + if (cancelled) { + return; + } + + while (i != len && e != n) { + T t = a[i]; + + if (t == null) { + s.onError(new NullPointerException("The " + i + "th array element was null")); + return; + } + + s.onNext(t); + + if (cancelled) { + return; + } + + i++; + e++; + } + + if (i == len) { + s.onComplete(); + return; + } + + n = requested; + + if (n == e) { + index = i; + n = REQUESTED.addAndGet(this, -e); + if (n == 0) { + return; + } + e = 0; + } + } + } + + void fastPath() { + final T[] a = array; + final int len = a.length; + final Subscriber s = actual; + + for (int i = index; i != len; i++) { + if (cancelled) { + return; + } + + T t = a[i]; + + if (t == null) { + s.onError(new NullPointerException("The " + i + "th array element was null")); + return; + } + + s.onNext(t); + } + if (cancelled) { + return; + } + s.onComplete(); + } + + @Override + public void cancel() { + cancelled = true; + } + + @Override + @Nullable + public T poll() { + int i = index; + T[] a = array; + if (i != a.length) { + T t = a[i]; + Objects.requireNonNull(t); + index = i + 1; + return t; + } + return null; + } + + @Override + public boolean isEmpty() { + return index == array.length; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void clear() { + index = array.length; + } + + @Override + public int size() { + return array.length - index; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return isEmpty(); + if (key == Attr.BUFFERED) return size(); + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + + return InnerProducer.super.scanUnsafe(key); + } + } + + static final class ArrayConditionalSubscription + implements InnerProducer, SynchronousSubscription { + + final ConditionalSubscriber actual; + + final T[] array; + + int index; + + volatile boolean cancelled; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ArrayConditionalSubscription.class, + "requested"); + + ArrayConditionalSubscription(ConditionalSubscriber actual, T[] array) { + this.actual = actual; + this.array = array; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (Operators.addCap(REQUESTED, this, n) == 0) { + if (n == Long.MAX_VALUE) { + fastPath(); + } + else { + slowPath(n); + } + } + } + } + + void slowPath(long n) { + final T[] a = array; + final int len = a.length; + final ConditionalSubscriber s = actual; + + int i = index; + int e = 0; + + for (; ; ) { + if (cancelled) { + return; + } + + while (i != len && e != n) { + T t = a[i]; + + if (t == null) { + s.onError(new NullPointerException("The " + i + "th array element was null")); + return; + } + + boolean b = s.tryOnNext(t); + + if (cancelled) { + return; + } + + i++; + if (b) { + e++; + } + } + + if (i == len) { + s.onComplete(); + return; + } + + n = requested; + + if (n == e) { + index = i; + n = REQUESTED.addAndGet(this, -e); + if (n == 0) { + return; + } + e = 0; + } + } + } + + void fastPath() { + final T[] a = array; + final int len = a.length; + final Subscriber s = actual; + + for (int i = index; i != len; i++) { + if (cancelled) { + return; + } + + T t = a[i]; + + if (t == null) { + s.onError(new NullPointerException("The " + i + "th array element was null")); + return; + } + + s.onNext(t); + } + if (cancelled) { + return; + } + s.onComplete(); + } + + @Override + public void cancel() { + cancelled = true; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return isEmpty(); + if (key == Attr.BUFFERED) return size(); + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + @Nullable + public T poll() { + int i = index; + T[] a = array; + if (i != a.length) { + T t = Objects.requireNonNull(a[i], "Array returned null value"); + index = i + 1; + return t; + } + return null; + } + + @Override + public boolean isEmpty() { + return index == array.length; + } + + @Override + public void clear() { + index = array.length; + } + + @Override + public int size() { + return array.length - index; + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxAutoConnect.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxAutoConnect.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxAutoConnect.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.function.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Connects to the underlying Flux once the given amount of Subscribers + * subscribed. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxAutoConnect extends Flux + implements Scannable { + + final ConnectableFlux source; + + final Consumer cancelSupport; + + volatile int remaining; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater REMAINING = + AtomicIntegerFieldUpdater.newUpdater(FluxAutoConnect.class, "remaining"); + + + FluxAutoConnect(ConnectableFlux source, + int n, Consumer cancelSupport) { + if (n <= 0) { + throw new IllegalArgumentException("n > required but it was " + n); + } + this.source = Objects.requireNonNull(source, "source"); + this.cancelSupport = Objects.requireNonNull(cancelSupport, "cancelSupport"); + REMAINING.lazySet(this, n); + } + + @Override + public void subscribe(CoreSubscriber actual) { + source.subscribe(actual); + if (remaining > 0 && REMAINING.decrementAndGet(this) == 0) { + source.connect(cancelSupport); + } + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + if (key == Attr.CAPACITY) return remaining; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxAutoConnectFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxAutoConnectFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxAutoConnectFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.function.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Connects to the underlying Flux once the given amount of Subscribers + * subscribed. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxAutoConnectFuseable extends Flux + implements Scannable, Fuseable { + + final ConnectableFlux source; + + final Consumer cancelSupport; + + volatile int remaining; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater REMAINING = + AtomicIntegerFieldUpdater.newUpdater(FluxAutoConnectFuseable.class, "remaining"); + + + FluxAutoConnectFuseable(ConnectableFlux source, + int n, Consumer cancelSupport) { + if (n <= 0) { + throw new IllegalArgumentException("n > required but it was " + n); + } + this.source = Objects.requireNonNull(source, "source"); + this.cancelSupport = Objects.requireNonNull(cancelSupport, "cancelSupport"); + REMAINING.lazySet(this, n); + } + + @Override + public void subscribe(CoreSubscriber actual) { + source.subscribe(actual); + if (remaining > 0 && REMAINING.decrementAndGet(this) == 0) { + source.connect(cancelSupport); + } + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + if (key == Attr.CAPACITY) return remaining; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxBuffer.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxBuffer.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxBuffer.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2016-2021 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.ArrayDeque; +import java.util.Collection; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Buffers a certain number of subsequent elements and emits the buffers. + * + * @param the source value type + * @param the buffer collection type + * + * @see Reactive-Streams-Commons + */ +final class FluxBuffer> extends InternalFluxOperator { + + final int size; + + final int skip; + + final Supplier bufferSupplier; + + FluxBuffer(Flux source, int size, Supplier bufferSupplier) { + this(source, size, size, bufferSupplier); + } + + FluxBuffer(Flux source, + int size, + int skip, + Supplier bufferSupplier) { + super(source); + if (size <= 0) { + throw new IllegalArgumentException("size > 0 required but it was " + size); + } + + if (skip <= 0) { + throw new IllegalArgumentException("skip > 0 required but it was " + skip); + } + + this.size = size; + this.skip = skip; + this.bufferSupplier = Objects.requireNonNull(bufferSupplier, "bufferSupplier"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (size == skip) { + return new BufferExactSubscriber<>(actual, size, bufferSupplier); + } + else if (skip > size) { + return new BufferSkipSubscriber<>(actual, size, skip, bufferSupplier); + } + else { + return new BufferOverlappingSubscriber<>(actual, size, skip, bufferSupplier); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class BufferExactSubscriber> + implements InnerOperator { + + final CoreSubscriber actual; + + final Supplier bufferSupplier; + + final int size; + + C buffer; + + Subscription s; + + boolean done; + + BufferExactSubscriber(CoreSubscriber actual, + int size, + Supplier bufferSupplier) { + this.actual = actual; + this.size = size; + this.bufferSupplier = bufferSupplier; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + s.request(Operators.multiplyCap(n, size)); + } + } + + @Override + public void cancel() { + s.cancel(); + Operators.onDiscardMultiple(buffer, actual.currentContext()); + } + + @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; + } + + C b = buffer; + if (b == null) { + try { + b = Objects.requireNonNull(bufferSupplier.get(), + "The bufferSupplier returned a null buffer"); + } + catch (Throwable e) { + Context ctx = actual.currentContext(); + onError(Operators.onOperatorError(s, e, t, ctx)); + Operators.onDiscard(t, ctx); //this is in no buffer + return; + } + buffer = b; + } + + b.add(t); + + if (b.size() == size) { + buffer = null; + actual.onNext(b); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + actual.onError(t); + Operators.onDiscardMultiple(buffer, actual.currentContext()); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + C b = buffer; + + if (b != null && !b.isEmpty()) { + actual.onNext(b); + } + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.BUFFERED) { + C b = buffer; + return b != null ? b.size() : 0; + } + if (key == Attr.CAPACITY) return size; + if (key == Attr.PREFETCH) return size; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + } + + static final class BufferSkipSubscriber> + implements InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + + final Supplier bufferSupplier; + + final int size; + + final int skip; + + C buffer; + + Subscription s; + + boolean done; + + long index; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(BufferSkipSubscriber.class, "wip"); + + BufferSkipSubscriber(CoreSubscriber actual, + int size, + int skip, + Supplier bufferSupplier) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.size = size; + this.skip = skip; + this.bufferSupplier = bufferSupplier; + } + + @Override + public void request(long n) { + if (!Operators.validate(n)) { + return; + } + + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + // n full buffers + long u = Operators.multiplyCap(n, size); + // + (n - 1) gaps + long v = Operators.multiplyCap(skip - size, n - 1); + + s.request(Operators.addCap(u, v)); + } + else { + // n full buffer + gap + s.request(Operators.multiplyCap(skip, n)); + } + } + + @Override + public void cancel() { + s.cancel(); + Operators.onDiscardMultiple(buffer, this.ctx); + } + + @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, this.ctx); + return; + } + + C b = buffer; + + long i = index; + + if (i % skip == 0L) { + try { + b = Objects.requireNonNull(bufferSupplier.get(), + "The bufferSupplier returned a null buffer"); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, this.ctx)); + Operators.onDiscard(t, this.ctx); //t hasn't got a chance to end up in any buffer + return; + } + + buffer = b; + } + + if (b != null) { + b.add(t); + if (b.size() == size) { + buffer = null; + actual.onNext(b); + } + } + else { + //dropping + Operators.onDiscard(t, this.ctx); + } + + index = i + 1; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, this.ctx); + return; + } + + done = true; + C b = buffer; + buffer = null; + + actual.onError(t); + Operators.onDiscardMultiple(b, this.ctx); + } + + @Override + public void onComplete() { + if (done) { + return; + } + + done = true; + C b = buffer; + buffer = null; + + if (b != null) { + actual.onNext(b); + } + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CAPACITY) return size; + if (key == Attr.BUFFERED) { + C b = buffer; + return b != null ? b.size() : 0; + } + if (key == Attr.PREFETCH) return size; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + } + + static final class BufferOverlappingSubscriber> + extends ArrayDeque + implements BooleanSupplier, InnerOperator { + + final CoreSubscriber actual; + + final Supplier bufferSupplier; + + final int size; + + final int skip; + + Subscription s; + + boolean done; + + long index; + + volatile boolean cancelled; + + long produced; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(BufferOverlappingSubscriber.class, + "once"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(BufferOverlappingSubscriber.class, + "requested"); + + BufferOverlappingSubscriber(CoreSubscriber actual, + int size, + int skip, + Supplier bufferSupplier) { + this.actual = actual; + this.size = size; + this.skip = skip; + this.bufferSupplier = bufferSupplier; + } + + @Override + public boolean getAsBoolean() { + return cancelled; + } + + @Override + public void request(long n) { + + if (!Operators.validate(n)) { + return; + } + + if (DrainUtils.postCompleteRequest(n, + actual, + this, + REQUESTED, + this, + this)) { + return; + } + + if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { + // (n - 1) skips + long u = Operators.multiplyCap(skip, n - 1); + + // + 1 full buffer + long r = Operators.addCap(size, u); + s.request(r); + } + else { + // n skips + long r = Operators.multiplyCap(skip, n); + s.request(r); + } + } + + @Override + public void cancel() { + cancelled = true; + s.cancel(); + clear(); + } + + @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; + } + + long i = index; + + if (i % skip == 0L) { + C b; + + try { + b = Objects.requireNonNull(bufferSupplier.get(), + "The bufferSupplier returned a null buffer"); + } + catch (Throwable e) { + Context ctx = actual.currentContext(); + onError(Operators.onOperatorError(s, e, t, ctx)); + Operators.onDiscard(t, ctx); //didn't get a chance to be added to a buffer + return; + } + + offer(b); + } + + C b = peek(); + + if (b != null && b.size() + 1 == size) { + poll(); + + b.add(t); + + actual.onNext(b); + + produced++; + } + + for (C b0 : this) { + b0.add(t); + } + + index = i + 1; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + done = true; + clear(); + + actual.onError(t); + } + + @Override + public void clear() { + Context ctx = actual.currentContext(); + for(C b: this) { + Operators.onDiscardMultiple(b, ctx); + } + super.clear(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + + done = true; + long p = produced; + if (p != 0L) { + Operators.produced(REQUESTED,this, p); + } + DrainUtils.postComplete(actual, this, REQUESTED, this, this); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.CAPACITY) return size() * size; + if (key == Attr.BUFFERED) return stream().mapToInt(Collection::size).sum(); + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferBoundary.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferBoundary.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferBoundary.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2016-2021 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.Collection; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Supplier; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +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; + +/** + * Buffers elements into custom collections where the buffer boundary is signalled + * by another publisher. + * + * @param the source value type + * @param the element type of the boundary publisher (irrelevant) + * @param the output collection type + * + * @see Reactive-Streams-Commons + */ +final class FluxBufferBoundary> + extends InternalFluxOperator { + + final Publisher other; + + final Supplier bufferSupplier; + + FluxBufferBoundary(Flux source, + Publisher other, + Supplier bufferSupplier) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + this.bufferSupplier = Objects.requireNonNull(bufferSupplier, "bufferSupplier"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + C buffer = Objects.requireNonNull(bufferSupplier.get(), + "The bufferSupplier returned a null buffer"); + + BufferBoundaryMain parent = + new BufferBoundaryMain<>( + source instanceof FluxInterval ? + actual : Operators.serialize(actual), + buffer, bufferSupplier); + + actual.onSubscribe(parent); + + other.subscribe(parent.other); + + return parent; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == RUN_STYLE) return SYNC; + return super.scanUnsafe(key); + } + + static final class BufferBoundaryMain> + implements InnerOperator { + + final Supplier bufferSupplier; + final CoreSubscriber actual; + final Context ctx; + + final BufferBoundaryOther other; + + C buffer; + + volatile Subscription s; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(BufferBoundaryMain.class, + Subscription.class, + "s"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(BufferBoundaryMain.class, "requested"); + + BufferBoundaryMain(CoreSubscriber actual, + C buffer, + Supplier bufferSupplier) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.buffer = buffer; + this.bufferSupplier = bufferSupplier; + this.other = new BufferBoundaryOther<>(this); + } + + @Override + public CoreSubscriber actual() { + return 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.CAPACITY) { + C buffer = this.buffer; + return buffer != null ? buffer.size() : 0; + } + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == RUN_STYLE) return SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + Operators.terminate(S, this); + Operators.onDiscardMultiple(buffer, this.ctx); + other.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + synchronized (this) { + C b = buffer; + if (b != null) { + b.add(t); + return; + } + } + + Operators.onNextDropped(t, this.ctx); + } + + @Override + public void onError(Throwable t) { + if(Operators.terminate(S, this)) { + C b; + synchronized (this) { + b = buffer; + buffer = null; + } + + other.cancel(); + actual.onError(t); + Operators.onDiscardMultiple(b, this.ctx); + return; + } + Operators.onErrorDropped(t, this.ctx); + } + + @Override + public void onComplete() { + if(Operators.terminate(S, this)) { + C b; + synchronized (this) { + b = buffer; + buffer = null; + } + + other.cancel(); + if (!b.isEmpty()) { + if (emit(b)) { + actual.onComplete(); + } //failed emit will discard buffer's elements + } + else { + actual.onComplete(); + } + } + } + void otherComplete() { + Subscription s = S.getAndSet(this, Operators.cancelledSubscription()); + if(s != Operators.cancelledSubscription()) { + C b; + synchronized (this) { + b = buffer; + buffer = null; + } + + if(s != null){ + s.cancel(); + } + + if (b != null && !b.isEmpty()) { + if (emit(b)) { + actual.onComplete(); + } //failed emit will discard buffer content + } + else { + actual.onComplete(); + } + } + } + + void otherError(Throwable t){ + Subscription s = S.getAndSet(this, Operators.cancelledSubscription()); + if(s != Operators.cancelledSubscription()) { + C b; + synchronized (this) { + b = buffer; + buffer = null; + } + + if(s != null){ + s.cancel(); + } + + actual.onError(t); + Operators.onDiscardMultiple(b, this.ctx); + return; + } + Operators.onErrorDropped(t, this.ctx); + } + + void otherNext() { + C c; + + try { + c = Objects.requireNonNull(bufferSupplier.get(), + "The bufferSupplier returned a null buffer"); + } + catch (Throwable e) { + otherError(Operators.onOperatorError(other, e, this.ctx)); + return; + } + + C b; + synchronized (this) { + b = buffer; + buffer = c; + } + + if (b == null || b.isEmpty()) { + return; + } + + emit(b); + } + + boolean emit(C b) { + long r = requested; + if (r != 0L) { + actual.onNext(b); + if (r != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + return true; + } + else { + actual.onError(Operators.onOperatorError(this, Exceptions + .failWithOverflow(), b, this.ctx)); + Operators.onDiscardMultiple(b, this.ctx); + return false; + } + } + } + + static final class BufferBoundaryOther extends Operators.DeferredSubscription + implements InnerConsumer { + + final BufferBoundaryMain main; + + BufferBoundaryOther(BufferBoundaryMain main) { + this.main = main; + } + + @Override + public void onSubscribe(Subscription s) { + if (set(s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) { + return main; + } + if (key == RUN_STYLE) { + return SYNC; + } + return super.scanUnsafe(key); + } + + @Override + public void onNext(U t) { + main.otherNext(); + } + + @Override + public void onError(Throwable t) { + main.otherError(t); + } + + @Override + public void onComplete() { + main.otherComplete(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferPredicate.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferPredicate.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferPredicate.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2016-2021 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.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiPredicate; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * 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 + * several modes: + *

    + *
  • {@code Until}: A new buffer starts when the predicate returns true. The + * element that just matched the predicate is the last in the previous buffer.
  • + *
  • {@code UntilOther}: A new buffer starts when the predicate returns true. The + * element that just matched the predicate is the first in the new buffer.
  • + *
  • {@code While}: A new buffer starts when the predicate stops matching. The + * non-matching elements are simply discarded.
  • + *
+ * + * @param the source value type + * @param the output collection type + * @see Reactive-Streams-Commons + */ +final class FluxBufferPredicate> + extends InternalFluxOperator { + + public enum Mode { + UNTIL, UNTIL_CUT_BEFORE, WHILE + } + + final Predicate predicate; + + final Supplier bufferSupplier; + + final Mode mode; + + FluxBufferPredicate(Flux source, Predicate predicate, + Supplier bufferSupplier, Mode mode) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + this.bufferSupplier = Objects.requireNonNull(bufferSupplier, "bufferSupplier"); + this.mode = mode; + } + + @Override + public int getPrefetch() { + return 1; //this operator changes the downstream request to 1 in the source + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + C initialBuffer = Objects.requireNonNull(bufferSupplier.get(), + "The bufferSupplier returned a null initial buffer"); + + BufferPredicateSubscriber parent = new BufferPredicateSubscriber<>(actual, + initialBuffer, bufferSupplier, predicate, mode); + + return parent; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class BufferPredicateSubscriber> + extends AbstractQueue + implements ConditionalSubscriber, InnerOperator, BooleanSupplier { + + final CoreSubscriber actual; + + final Supplier bufferSupplier; + + final Mode mode; + + final Predicate predicate; + + @Nullable + C buffer; + + boolean done; + + volatile boolean fastpath; + + volatile long requestedBuffers; + + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED_BUFFERS = + AtomicLongFieldUpdater.newUpdater(BufferPredicateSubscriber.class, + "requestedBuffers"); + + volatile long requestedFromSource; + + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED_FROM_SOURCE = + AtomicLongFieldUpdater.newUpdater(BufferPredicateSubscriber.class, + "requestedFromSource"); + + volatile Subscription s; + + static final AtomicReferenceFieldUpdater S = AtomicReferenceFieldUpdater.newUpdater + (BufferPredicateSubscriber.class, Subscription.class, "s"); + + BufferPredicateSubscriber(CoreSubscriber actual, C initialBuffer, + Supplier bufferSupplier, Predicate predicate, Mode mode) { + this.actual = actual; + this.buffer = initialBuffer; + this.bufferSupplier = bufferSupplier; + this.predicate = predicate; + this.mode = mode; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (n == Long.MAX_VALUE) { + // here we request everything from the source. switching to + // fastpath will avoid unnecessary request(1) during filling + fastpath = true; + REQUESTED_BUFFERS.set(this, Long.MAX_VALUE); + REQUESTED_FROM_SOURCE.set(this, Long.MAX_VALUE); + s.request(Long.MAX_VALUE); + } + else { + // Requesting from source may have been interrupted if downstream + // received enough buffer (requested == 0), so this new request for + // buffer should resume progressive filling from upstream. We can + // directly request the same as the number of needed buffers (if + // buffers turn out 1-sized then we'll have everything, otherwise + // we'll continue requesting one by one) + if (!DrainUtils.postCompleteRequest(n, + actual, + this, REQUESTED_BUFFERS, + this, + this)) { + Operators.addCap(REQUESTED_FROM_SOURCE, this, n); + s.request(n); + } + } + } + } + + @Override + public void cancel() { + C b; + synchronized (this) { + b = buffer; + buffer = null; + Operators.onDiscardMultiple(b, actual.currentContext()); + } + cleanup(); + Operators.terminate(S, this); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t)) { + s.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return true; + } + + boolean match; + try { + match = predicate.test(t); + } + catch (Throwable e) { + Context ctx = actual.currentContext(); + onError(Operators.onOperatorError(s, e, t, ctx)); //will discard the buffer + Operators.onDiscard(t, ctx); + return true; + } + + if (mode == Mode.UNTIL && match) { + if (cancelledWhileAdding(t)) { + return true; + } + onNextNewBuffer(); + } + else if (mode == Mode.UNTIL_CUT_BEFORE && match) { + onNextNewBuffer(); + if (cancelledWhileAdding(t)) { + return true; + } + } + else if (mode == Mode.WHILE && !match) { + onNextNewBuffer(); + } + else { + if (cancelledWhileAdding(t)) { + return true; + } + } + + if (fastpath) { + return true; + } + + boolean isNotExpectingFromSource = REQUESTED_FROM_SOURCE.decrementAndGet(this) == 0; + boolean isStillExpectingBuffer = REQUESTED_BUFFERS.get(this) > 0; + if (isNotExpectingFromSource && isStillExpectingBuffer + && REQUESTED_FROM_SOURCE.compareAndSet(this, 0, 1)) { + return false; //explicitly mark as "needing more", either in attached conditional or onNext() + } + return true; + } + + boolean cancelledWhileAdding(T value) { + synchronized (this) { + C b = buffer; + if (b == null || s == Operators.cancelledSubscription()) { + Operators.onDiscard(value, actual.currentContext()); + return true; + } + else { + b.add(value); + return false; + } + } + } + + @Nullable + C triggerNewBuffer() { + C b; + synchronized (this) { + b = buffer; + + if (b == null || s == Operators.cancelledSubscription()) { + return null; + } + } + + if (b.isEmpty()) { + //emit nothing and we'll reuse the same buffer + return null; + } + + //we'll create a new buffer + C c; + + try { + c = Objects.requireNonNull(bufferSupplier.get(), + "The bufferSupplier returned a null buffer"); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return null; + } + + synchronized (this) { + if (buffer == null) { + return null; + } + buffer = c; + } + return b; + } + + private void onNextNewBuffer() { + C b = triggerNewBuffer(); + if (b != null) { + if (fastpath) { + actual.onNext(b); + return; + } + long r = REQUESTED_BUFFERS.getAndDecrement(this); + if (r > 0) { + actual.onNext(b); + return; + } + cancel(); + actual.onError(Exceptions.failWithOverflow("Could not emit buffer due to lack of requests")); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + C b; + synchronized (this) { + b = buffer; + buffer = null; + } + cleanup(); + //safe to discard the buffer outside synchronized block + //since onNext MUST NOT happen in parallel with onError + Operators.onDiscardMultiple(b, actual.currentContext()); + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + cleanup(); + DrainUtils.postComplete(actual, this, REQUESTED_BUFFERS, this, this); + } + + void cleanup() { + // necessary cleanup if predicate contains a state + if (predicate instanceof Disposable) { + ((Disposable) predicate).dispose(); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return getAsBoolean(); + if (key == Attr.CAPACITY) { + C b = buffer; + return b != null ? b.size() : 0; + } + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requestedBuffers; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public boolean getAsBoolean() { + return s == Operators.cancelledSubscription(); + } + + @Override + public Iterator iterator() { + if (isEmpty()) { + return Collections.emptyIterator(); + } + return Collections.singleton(buffer).iterator(); + } + + @Override + public boolean offer(C objects) { + throw new IllegalArgumentException(); + } + + @Override + @Nullable + public C poll() { + C b = buffer; + if (b != null && !b.isEmpty()) { + synchronized (this) { + buffer = null; + } + return b; + } + return null; + } + + @Override + @Nullable + public C peek() { + return buffer; + } + + @Override + public int size() { + C b = buffer; + return b == null || b.isEmpty() ? 0 : 1; + } + + @Override + public String toString() { + return "FluxBufferPredicate"; + } + } + + static class ChangedPredicate implements Predicate, Disposable { + + private Function keySelector; + private BiPredicate keyComparator; + private K lastKey; + + ChangedPredicate(Function keySelector, + BiPredicate keyComparator) { + this.keySelector = keySelector; + this.keyComparator = keyComparator; + } + + @Override + public void dispose() { + lastKey = null; + } + + @Override + public boolean test(T t) { + K k = keySelector.apply(t); + + if (null == lastKey) { + lastKey = k; + return false; + } + + boolean match; + match = keyComparator.test(lastKey, k); + lastKey = k; + + return !match; + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferTimeout.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferTimeout.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferTimeout.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2016-2021 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.Collection; +import java.util.Objects; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Supplier; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; +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; + + FluxBufferTimeout(Flux source, + int maxSize, + long timespan, + TimeUnit unit, + Scheduler timer, + Supplier bufferSupplier) { + 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.timer = Objects.requireNonNull(timer, "Timer"); + this.timespan = timespan; + this.unit = Objects.requireNonNull(unit, "unit"); + this.batchSize = maxSize; + this.bufferSupplier = Objects.requireNonNull(bufferSupplier, "bufferSupplier"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new BufferTimeoutSubscriber<>( + Operators.serialize(actual), + batchSize, + timespan, + unit, + timer.createWorker(), + bufferSupplier + ); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return timer; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return super.scanUnsafe(key); + } + + final static class BufferTimeoutSubscriber> + implements InnerOperator { + + final CoreSubscriber actual; + + 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; + + final int batchSize; + final long timespan; + final TimeUnit unit; + final Scheduler.Worker timer; + final Runnable flushTask; + + protected Subscription subscription; + + volatile int terminated = + NOT_TERMINATED; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater TERMINATED = + AtomicIntegerFieldUpdater.newUpdater(BufferTimeoutSubscriber.class, "terminated"); + + + volatile long requested; + + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(BufferTimeoutSubscriber.class, "requested"); + + volatile long outstanding; + + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater OUTSTANDING = + AtomicLongFieldUpdater.newUpdater(BufferTimeoutSubscriber.class, "outstanding"); + + volatile int index = 0; + + static final AtomicIntegerFieldUpdater INDEX = + AtomicIntegerFieldUpdater.newUpdater(BufferTimeoutSubscriber.class, "index"); + + + volatile Disposable timespanRegistration; + + final Supplier bufferSupplier; + + volatile C values; + + BufferTimeoutSubscriber(CoreSubscriber actual, + int maxSize, + long timespan, + TimeUnit unit, + Scheduler.Worker timer, + Supplier bufferSupplier) { + this.actual = actual; + this.timespan = timespan; + this.unit = unit; + this.timer = timer; + this.flushTask = () -> { + if (terminated == NOT_TERMINATED) { + int index; + for(;;){ + index = this.index; + if(index == 0){ + return; + } + if(INDEX.compareAndSet(this, index, 0)){ + break; + } + } + flushCallback(null); + } + }; + + this.batchSize = maxSize; + this.bufferSupplier = bufferSupplier; + } + + protected void doOnSubscribe() { + values = bufferSupplier.get(); + } + + void nextCallback(T value) { + synchronized (this) { + if (OUTSTANDING.decrementAndGet(this) < 0) + { + actual.onError(Exceptions.failWithOverflow("Unrequested element received")); + Context ctx = actual.currentContext(); + Operators.onDiscard(value, ctx); + Operators.onDiscardMultiple(values, ctx); + return; + } + + C v = values; + if(v == null) { + v = Objects.requireNonNull(bufferSupplier.get(), + "The bufferSupplier returned a null buffer"); + values = v; + } + v.add(value); + } + } + + void flushCallback(@Nullable T ev) { //TODO investigate ev not used + final C v; + boolean flush = false; + synchronized (this) { + v = values; + if (v != null && !v.isEmpty()) { + values = bufferSupplier.get(); + flush = true; + } + } + + if (flush) { + long r = requested; + if (r != 0L) { + if (r != Long.MAX_VALUE) { + long next; + for (;;) { + next = r - 1; + if (REQUESTED.compareAndSet(this, r, next)) { + actual.onNext(v); + return; + } + + r = requested; + if (r <= 0L) { + break; + } + } + } + else { + actual.onNext(v); + return; + } + } + + cancel(); + actual.onError(Exceptions.failWithOverflow( + "Could not emit buffer due to lack of requests")); + Operators.onDiscardMultiple(v, this.actual.currentContext()); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return 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 batchSize; + if (key == Attr.BUFFERED) return batchSize - index; + if (key == Attr.RUN_ON) return timer; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onNext(final T value) { + int index; + for(;;){ + index = this.index + 1; + if(INDEX.compareAndSet(this, index - 1, index)){ + break; + } + } + + if (index == 1) { + try { + timespanRegistration = timer.schedule(flushTask, timespan, unit); + } + catch (RejectedExecutionException ree) { + Context ctx = actual.currentContext(); + onError(Operators.onRejectedExecution(ree, subscription, null, value, ctx)); + Operators.onDiscard(value, ctx); + return; + } + } + + nextCallback(value); + + if (this.index % batchSize == 0) { + this.index = 0; + if (timespanRegistration != null) { + timespanRegistration.dispose(); + timespanRegistration = null; + } + flushCallback(value); + } + } + + void checkedComplete() { + try { + flushCallback(null); + } + finally { + actual.onComplete(); + } + } + + /** + * @return has this {@link Subscriber} terminated with success ? + */ + final boolean isCompleted() { + return terminated == TERMINATED_WITH_SUCCESS; + } + + /** + * @return has this {@link Subscriber} terminated with an error ? + */ + final boolean isFailed() { + return terminated == TERMINATED_WITH_ERROR; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + if (terminated != NOT_TERMINATED) { + return; + } + if (batchSize == Integer.MAX_VALUE || n == Long.MAX_VALUE) { + requestMore(Long.MAX_VALUE); + } + else { + long requestLimit = Operators.multiplyCap(requested, batchSize); + requestMore(requestLimit - outstanding); + } + } + } + + final void requestMore(long n) { + Subscription s = this.subscription; + if (s != null) { + Operators.addCap(OUTSTANDING, this, n); + s.request(n); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onComplete() { + if (TERMINATED.compareAndSet(this, NOT_TERMINATED, TERMINATED_WITH_SUCCESS)) { + timer.dispose(); + checkedComplete(); + } + } + + @Override + public void onError(Throwable throwable) { + if (TERMINATED.compareAndSet(this, NOT_TERMINATED, TERMINATED_WITH_ERROR)) { + timer.dispose(); + Context ctx = actual.currentContext(); + synchronized (this) { + C v = values; + if(v != null) { + Operators.onDiscardMultiple(v, ctx); + v.clear(); + values = null; + } + } + actual.onError(throwable); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.subscription, s)) { + this.subscription = s; + doOnSubscribe(); + actual.onSubscribe(this); + } + } + + @Override + public void cancel() { + if (TERMINATED.compareAndSet(this, NOT_TERMINATED, TERMINATED_WITH_CANCEL)) { + timer.dispose(); + Subscription s = this.subscription; + if (s != null) { + this.subscription = null; + s.cancel(); + } + C v = values; + if (v != null) { + Operators.onDiscardMultiple(v, actual.currentContext()); + v.clear(); + } + } + } + } +} + Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxBufferWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,582 @@ +/* + * Copyright (c) 2016-2021 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.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.reactivestreams.Publisher; +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.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Buffers elements into possibly overlapping buffers whose boundaries are determined + * by a start Publisher's element and a signal of a derived Publisher + * + * @param the source value type + * @param the value type of the publisher opening the buffers + * @param the value type of the publisher closing the individual buffers + * @param the collection type that holds the buffered values + * + * @see Reactive-Streams-Commons + */ +final class FluxBufferWhen> + extends InternalFluxOperator { + + final Publisher start; + + final Function> end; + + final Supplier bufferSupplier; + + final Supplier> queueSupplier; + + FluxBufferWhen(Flux source, + Publisher start, + Function> end, + Supplier bufferSupplier, + Supplier> queueSupplier) { + super(source); + this.start = Objects.requireNonNull(start, "start"); + this.end = Objects.requireNonNull(end, "end"); + this.bufferSupplier = Objects.requireNonNull(bufferSupplier, "bufferSupplier"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + BufferWhenMainSubscriber main = + new BufferWhenMainSubscriber<>(actual, bufferSupplier, queueSupplier, start, end); + + actual.onSubscribe(main); + + BufferWhenOpenSubscriber bos = new BufferWhenOpenSubscriber<>(main); + if (main.subscribers.add(bos)) { + start.subscribe(bos); + return main; + } + else { + return null; + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class BufferWhenMainSubscriber> + implements InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + final Publisher bufferOpen; + final Function> bufferClose; + final Supplier bufferSupplier; + final Disposable.Composite subscribers; + final Queue queue; + + volatile long requested; + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(BufferWhenMainSubscriber.class, "requested"); + + volatile Subscription s; + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(BufferWhenMainSubscriber.class, Subscription.class, "s"); + + volatile Throwable errors; + static final AtomicReferenceFieldUpdater + ERRORS = + AtomicReferenceFieldUpdater.newUpdater(BufferWhenMainSubscriber.class, Throwable.class, "errors"); + + volatile int windows; + static final AtomicIntegerFieldUpdater WINDOWS = + AtomicIntegerFieldUpdater.newUpdater(BufferWhenMainSubscriber.class, "windows"); + + volatile boolean done; + volatile boolean cancelled; + + long index; + LinkedHashMap buffers; //linkedHashMap important to keep the buffer order on final drain + long emitted; + + BufferWhenMainSubscriber(CoreSubscriber actual, + Supplier bufferSupplier, Supplier> queueSupplier, + Publisher bufferOpen, + Function> bufferClose) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.bufferOpen = bufferOpen; + this.bufferClose = bufferClose; + this.bufferSupplier = bufferSupplier; + this.queue = queueSupplier.get(); + this.buffers = new LinkedHashMap<>(); + this.subscribers = Disposables.composite(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onNext(T t) { + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + if (bufs.isEmpty()) { + Operators.onDiscard(t, this.ctx); + return; + } + for (BUFFER b : bufs.values()) { + b.add(t); + } + } + } + + @Override + public void onError(Throwable t) { + if (Exceptions.addThrowable(ERRORS, this, t)) { + subscribers.dispose(); + Map bufs; + synchronized (this) { + bufs = buffers; + buffers = null; + } + done = true; + drain(); + if (bufs != null) { + for (BUFFER b : bufs.values()) { + Operators.onDiscardMultiple(b, this.ctx); + } + } + } + else { + Operators.onErrorDropped(t, this.ctx); + } + } + + @Override + public void onComplete() { + subscribers.dispose(); + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + for (BUFFER b : bufs.values()) { + queue.offer(b); + } + buffers = null; + } + done = true; + drain(); + } + + @Override + public void request(long n) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + + @Override + public void cancel() { + if (Operators.terminate(S, this)) { + cancelled = true; + subscribers.dispose(); + Map bufs; + synchronized (this) { + bufs = buffers; + buffers = null; + } + //first discard buffers that have been queued if they're not being drained... + if (WINDOWS.getAndIncrement(this) == 0) { + Operators.onDiscardQueueWithClear(queue, this.ctx, BUFFER::stream); + } + //...then discard unclosed buffers + if (bufs != null && !bufs.isEmpty()) { + for (BUFFER buffer : bufs.values()) { + Operators.onDiscardMultiple(buffer, this.ctx); + } + } + } + } + + void drain() { + if (WINDOWS.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + long e = emitted; + Subscriber a = actual; + Queue q = queue; + + for (;;) { + long r = requested; + + while (e != r) { + if (cancelled) { + Operators.onDiscardQueueWithClear(q, this.ctx, BUFFER::stream); + return; + } + + boolean d = done; + if (d && errors != null) { + Operators.onDiscardQueueWithClear(q, this.ctx, BUFFER::stream); + Throwable ex = Exceptions.terminate(ERRORS, this); + a.onError(ex); + return; + } + + BUFFER v = q.poll(); + boolean empty = v == null; + + if (d && empty) { + a.onComplete(); + return; + } + + if (empty) { + break; + } + + a.onNext(v); + e++; + } + + if (e == r) { + if (cancelled) { + Operators.onDiscardQueueWithClear(q, this.ctx, BUFFER::stream); + return; + } + + if (done) { + if (errors != null) { + Operators.onDiscardQueueWithClear(q, this.ctx, BUFFER::stream); + Throwable ex = Exceptions.terminate(ERRORS, this); + a.onError(ex); + return; + } + else if (q.isEmpty()) { + a.onComplete(); + return; + } + } + } + + emitted = e; + missed = WINDOWS.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void open(OPEN token) { + Publisher p; + BUFFER buf; + try { + buf = Objects.requireNonNull(bufferSupplier.get(), "The bufferSupplier returned a null Collection"); + p = Objects.requireNonNull(bufferClose.apply(token), "The bufferClose returned a null Publisher"); + } + catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + Operators.terminate(S, this); + if (Exceptions.addThrowable(ERRORS, this, ex)) { + subscribers.dispose(); + Map bufs; + synchronized (this) { + bufs = buffers; + buffers = null; + } + + done = true; + drain(); + if (bufs != null) { + for (BUFFER buffer : bufs.values()) { + Operators.onDiscardMultiple(buffer, this.ctx); + } + } + } + else { + Operators.onErrorDropped(ex, this.ctx); + } + return; + } + + long idx = index; + index = idx + 1; + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + bufs.put(idx, buf); + } + + BufferWhenCloseSubscriber bc = new BufferWhenCloseSubscriber<>(this, idx); + subscribers.add(bc); + p.subscribe(bc); + } + + void openComplete(BufferWhenOpenSubscriber os) { + subscribers.remove(os); + if (subscribers.size() == 0) { + Operators.terminate(S, this); + done = true; + drain(); + } + } + + void close(BufferWhenCloseSubscriber closer, long idx) { + subscribers.remove(closer); + boolean makeDone = false; + if (subscribers.size() == 0) { + makeDone = true; + Operators.terminate(S, this); + } + synchronized (this) { + Map bufs = buffers; + if (bufs == null) { + return; + } + queue.offer(buffers.remove(idx)); + } + if (makeDone) { + done = true; + } + drain(); + } + + void boundaryError(Disposable boundary, Throwable ex) { + Operators.terminate(S, this); + subscribers.remove(boundary); + if (Exceptions.addThrowable(ERRORS, this, ex)) { + subscribers.dispose(); + Map bufs; + synchronized (this) { + bufs = buffers; + buffers = null; + } + done = true; + drain(); + if (bufs != null) { + for (BUFFER buffer : bufs.values()) { + Operators.onDiscardMultiple(buffer, this.ctx); + } + } + } + else { + Operators.onErrorDropped(ex, this.ctx); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return actual; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.BUFFERED) return buffers.values() + .stream() + .mapToInt(Collection::size) + .sum(); + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.TERMINATED) return done; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.ERROR) return errors; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } + + static final class BufferWhenOpenSubscriber + implements Disposable, InnerConsumer { + + volatile Subscription subscription; + static final AtomicReferenceFieldUpdater SUBSCRIPTION = + AtomicReferenceFieldUpdater.newUpdater(BufferWhenOpenSubscriber.class, Subscription.class, "subscription"); + + final BufferWhenMainSubscriber parent; + + BufferWhenOpenSubscriber(BufferWhenMainSubscriber parent) { + this.parent = parent; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(SUBSCRIPTION, this, s)) { + subscription.request(Long.MAX_VALUE); + } + } + + @Override + public void dispose() { + Operators.terminate(SUBSCRIPTION, this); + } + + @Override + public boolean isDisposed() { + return subscription == Operators.cancelledSubscription(); + } + + @Override + public void onNext(OPEN t) { + parent.open(t); + } + + @Override + public void onError(Throwable t) { + SUBSCRIPTION.lazySet(this, Operators.cancelledSubscription()); + parent.boundaryError(this, t); + } + + @Override + public void onComplete() { + SUBSCRIPTION.lazySet(this, Operators.cancelledSubscription()); + parent.openComplete(this); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) return parent; + if (key == Attr.PARENT) return subscription; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return Long.MAX_VALUE; + if (key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } + + static final class BufferWhenCloseSubscriber> + implements Disposable, InnerConsumer { + + volatile Subscription subscription; + static final AtomicReferenceFieldUpdater SUBSCRIPTION = + AtomicReferenceFieldUpdater.newUpdater(BufferWhenCloseSubscriber.class, Subscription.class, "subscription"); + + final BufferWhenMainSubscriber parent; + final long index; + + + BufferWhenCloseSubscriber(BufferWhenMainSubscriber parent, long index) { + this.parent = parent; + this.index = index; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(SUBSCRIPTION, this, s)) { + subscription.request(Long.MAX_VALUE); + } + } + + @Override + public void dispose() { + Operators.terminate(SUBSCRIPTION, this); + } + + @Override + public boolean isDisposed() { + return subscription == Operators.cancelledSubscription(); + } + + @Override + public void onNext(Object t) { + Subscription s = subscription; + if (s != Operators.cancelledSubscription()) { + SUBSCRIPTION.lazySet(this, Operators.cancelledSubscription()); + s.cancel(); + parent.close(this, index); + } + } + + @Override + public void onError(Throwable t) { + if (subscription != Operators.cancelledSubscription()) { + SUBSCRIPTION.lazySet(this, Operators.cancelledSubscription()); + parent.boundaryError(this, t); + } + else { + Operators.onErrorDropped(t, parent.ctx); + } + } + + @Override + public void onComplete() { + if (subscription != Operators.cancelledSubscription()) { + SUBSCRIPTION.lazySet(this, Operators.cancelledSubscription()); + parent.close(this, index); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) return parent; + if (key == Attr.PARENT) return subscription; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return Long.MAX_VALUE; + if (key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxCallable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxCallable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxCallable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016-2021 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.Callable; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * For each subscriber, a Supplier is invoked and the returned value emitted. + * + * @param the value type; + */ +final class FluxCallable extends Flux implements Callable, Fuseable, SourceProducer { + + final Callable callable; + + FluxCallable(Callable callable) { + this.callable = callable; + } + + @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())); + } + } + + @Override + @Nullable + public T call() throws Exception { + return callable.call(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxCallableOnAssembly.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxCallableOnAssembly.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxCallableOnAssembly.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016-2021 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.Callable; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; + +/** + * Captures the current stacktrace when this publisher is created and makes it + * available/visible for debugging purposes from the inner Subscriber. + *

+ * Note that getting a stacktrace is a costly operation. + *

+ * The operator sanitizes the stacktrace and removes noisy entries such as:

    + *
  • java.lang.Thread entries
  • method references with source line of 1 (bridge + * methods)
  • Tomcat worker thread entries
  • JUnit setup
+ * + * @param the value type passing through + * @see https://github.com/reactor/reactive-streams-commons + */ +final class FluxCallableOnAssembly extends InternalFluxOperator + implements Fuseable, Callable, AssemblyOp { + + final AssemblySnapshot stacktrace; + + FluxCallableOnAssembly(Flux source, AssemblySnapshot stacktrace) { + super(source); + this.stacktrace = stacktrace; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return FluxOnAssembly.wrapSubscriber(actual, source, this, stacktrace); + } + + @SuppressWarnings("unchecked") + @Override + public T call() throws Exception { + return ((Callable) source).call(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL_METADATA) return !stacktrace.isCheckpoint; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public String stepName() { + return stacktrace.operatorAssemblyInformation(); + } + + @Override + public String toString() { + return stacktrace.operatorAssemblyInformation(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxCancelOn.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxCancelOn.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxCancelOn.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +final class FluxCancelOn extends InternalFluxOperator { + + final Scheduler scheduler; + + public FluxCancelOn(Flux source, Scheduler scheduler) { + super(source); + this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new CancelSubscriber(actual, scheduler); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return super.scanUnsafe(key); + } + + static final class CancelSubscriber + implements InnerOperator, Runnable { + + final CoreSubscriber actual; + final Scheduler scheduler; + + Subscription s; + + volatile int cancelled = 0; + static final AtomicIntegerFieldUpdater CANCELLED = + AtomicIntegerFieldUpdater.newUpdater(CancelSubscriber.class, "cancelled"); + + CancelSubscriber(CoreSubscriber actual, Scheduler scheduler) { + this.actual = actual; + this.scheduler = scheduler; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return cancelled == 1; + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void run() { + s.cancel(); + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + if (CANCELLED.compareAndSet(this, 0, 1)) { + try { + scheduler.schedule(this); + } + catch (RejectedExecutionException ree) { + throw Operators.onRejectedExecution(ree, actual.currentContext()); + } + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxCombineLatest.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxCombineLatest.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxCombineLatest.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,664 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; +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; + +/** + * Combines the latest values from multiple sources through a function. + * + * @param the value type of the sources + * @param the result type + * + * @see Reactive-Streams-Commons + */ +final class FluxCombineLatest extends Flux implements Fuseable, SourceProducer { + + final Publisher[] array; + + final Iterable> iterable; + + final Function combiner; + + final Supplier> queueSupplier; + + final int prefetch; + + FluxCombineLatest(Publisher[] array, + Function combiner, + Supplier> queueSupplier, int prefetch) { + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + + this.array = Objects.requireNonNull(array, "array"); + this.iterable = null; + this.combiner = Objects.requireNonNull(combiner, "combiner"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + this.prefetch = prefetch; + } + + FluxCombineLatest(Iterable> iterable, + Function combiner, + Supplier> queueSupplier, int prefetch) { + if (prefetch < 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + + this.array = null; + this.iterable = Objects.requireNonNull(iterable, "iterable"); + this.combiner = Objects.requireNonNull(combiner, "combiner"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + this.prefetch = prefetch; + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @SuppressWarnings("unchecked") + @Override + public void subscribe(CoreSubscriber actual) { + Publisher[] a = array; + int n; + if (a == null) { + n = 0; + a = new Publisher[8]; + + Iterator> it; + + try { + it = Objects.requireNonNull(iterable.iterator(), "The iterator returned is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + for (; ; ) { + + boolean b; + + try { + b = it.hasNext(); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (!b) { + break; + } + + Publisher p; + + try { + p = Objects.requireNonNull(it.next(), + "The Publisher returned by the iterator is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (n == a.length) { + Publisher[] c = new Publisher[n + (n >> 2)]; + System.arraycopy(a, 0, c, 0, n); + a = c; + } + a[n++] = p; + } + + } + else { + n = a.length; + } + + if (n == 0) { + Operators.complete(actual); + return; + } + if (n == 1) { + Function f = t -> combiner.apply(new Object[]{t}); + if (a[0] instanceof Fuseable) { + new FluxMapFuseable<>(from(a[0]), f).subscribe(actual); + return; + } + else if (!(actual instanceof QueueSubscription)) { + new FluxMap<>(from(a[0]), f).subscribe(actual); + return; + } + } + + Queue queue = queueSupplier.get(); + + CombineLatestCoordinator coordinator = + new CombineLatestCoordinator<>(actual, combiner, n, queue, prefetch); + + actual.onSubscribe(coordinator); + + coordinator.subscribe(a, 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 CombineLatestCoordinator + implements QueueSubscription, InnerProducer { + + final Function combiner; + final CombineLatestInner[] subscribers; + final Queue queue; + final Object[] latest; + final CoreSubscriber actual; + + boolean outputFused; + + int nonEmptySources; + + int completedSources; + + volatile boolean cancelled; + + volatile long requested; + + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(CombineLatestCoordinator.class, + "requested"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(CombineLatestCoordinator.class, + "wip"); + + volatile boolean done; + + volatile Throwable error; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + ERROR = + AtomicReferenceFieldUpdater.newUpdater(CombineLatestCoordinator.class, + Throwable.class, + "error"); + + CombineLatestCoordinator(CoreSubscriber actual, + Function combiner, + int n, + Queue queue, int prefetch) { + this.actual = actual; + this.combiner = combiner; + @SuppressWarnings("unchecked") CombineLatestInner[] a = + new CombineLatestInner[n]; + for (int i = 0; i < n; i++) { + a[i] = new CombineLatestInner<>(this, i, prefetch); + } + this.subscribers = a; + this.latest = new Object[n]; + this.queue = queue; + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + cancelAll(); + + if (WIP.getAndIncrement(this) == 0) { + clear(); + } + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.ERROR) return error; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + + return InnerProducer.super.scanUnsafe(key); + } + + void subscribe(Publisher[] sources, int n) { + CombineLatestInner[] a = subscribers; + + for (int i = 0; i < n; i++) { + if (done || cancelled) { + return; + } + sources[i].subscribe(a[i]); + } + } + + void innerValue(int index, T value) { + + boolean replenishInsteadOfDrain; + + synchronized (this) { + Object[] os = latest; + + int localNonEmptySources = nonEmptySources; + + if (os[index] == null) { + localNonEmptySources++; + nonEmptySources = localNonEmptySources; + } + + os[index] = value; + + if (os.length == localNonEmptySources) { + SourceAndArray sa = + new SourceAndArray(subscribers[index], os.clone()); + + if (!queue.offer(sa)) { + innerError(Operators.onOperatorError(this, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), actual.currentContext())); + return; + } + + replenishInsteadOfDrain = false; + } + else { + replenishInsteadOfDrain = true; + } + } + + if (replenishInsteadOfDrain) { + subscribers[index].requestOne(); + } + else { + drain(); + } + } + + void innerComplete(int index) { + synchronized (this) { + Object[] os = latest; + + if (os[index] != null) { + int localCompletedSources = completedSources + 1; + + if (localCompletedSources == os.length) { + done = true; + } + else { + completedSources = localCompletedSources; + return; + } + } + else { + done = true; + } + } + drain(); + } + + void innerError(Throwable e) { + + if (Exceptions.addThrowable(ERROR, this, e)) { + done = true; + drain(); + } + else { + discardQueue(queue); + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + void drainOutput() { + final CoreSubscriber a = actual; + final Queue q = queue; + + int missed = 1; + + for (; ; ) { + + if (cancelled) { + discardQueue(q); + return; + } + + Throwable ex = error; + if (ex != null) { + discardQueue(q); + a.onError(ex); + return; + } + + boolean d = done; + + boolean empty = q.isEmpty(); + + if (!empty) { + a.onNext(null); + } + + if (d && empty) { + a.onComplete(); + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drainAsync() { + final Queue q = queue; + + int missed = 1; + + for (; ; ) { + + long r = requested; + long e = 0L; + + while (e != r) { + boolean d = done; + + SourceAndArray v = q.poll(); + + boolean empty = v == null; + + if (checkTerminated(d, empty, q)) { + return; + } + + if (empty) { + break; + } + + R w; + + try { + w = Objects.requireNonNull(combiner.apply(v.array), "Combiner returned null"); + } + catch (Throwable ex) { + Context ctx = actual.currentContext(); + Operators.onDiscardMultiple(Stream.of(v.array), ctx); + + ex = Operators.onOperatorError(this, ex, v.array, ctx); + Exceptions.addThrowable(ERROR, this, ex); + //noinspection ConstantConditions + ex = Exceptions.terminate(ERROR, this); + actual.onError(ex); + return; + } + + actual.onNext(w); + + v.source.requestOne(); + + e++; + } + + if (e == r) { + if (checkTerminated(done, q.isEmpty(), q)) { + return; + } + } + + if (e != 0L && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + if (outputFused) { + drainOutput(); + } + else { + drainAsync(); + } + } + + boolean checkTerminated(boolean d, boolean empty, Queue q) { + if (cancelled) { + cancelAll(); + discardQueue(q); + return true; + } + + if (d) { + Throwable e = Exceptions.terminate(ERROR, this); + + if (e != null && e != Exceptions.TERMINATED) { + cancelAll(); + discardQueue(q); + actual.onError(e); + return true; + } + else if (empty) { + cancelAll(); + + actual.onComplete(); + return true; + } + } + return false; + } + + void cancelAll() { + for (CombineLatestInner inner : subscribers) { + inner.cancel(); + } + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & THREAD_BARRIER) != 0) { + return NONE; + } + int m = requestedMode & ASYNC; + outputFused = m != 0; + return m; + } + + @Override + @Nullable + public R poll() { + SourceAndArray e = queue.poll(); + if (e == null) { + return null; + } + R r = combiner.apply(e.array); + e.source.requestOne(); + return r; + } + + private void discardQueue(Queue q) { + Operators.onDiscardQueueWithClear(q, actual.currentContext(), SourceAndArray::toStream); + } + + @Override + public void clear() { + discardQueue(queue); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + public int size() { + return queue.size(); + } + } + + static final class CombineLatestInner + implements InnerConsumer { + + final CombineLatestCoordinator parent; + + final int index; + + final int prefetch; + + final int limit; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(CombineLatestInner.class, + Subscription.class, + "s"); + + int produced; + + CombineLatestInner(CombineLatestCoordinator parent, + int index, + int prefetch) { + this.parent = parent; + this.index = index; + this.prefetch = prefetch; + this.limit = Operators.unboundedOrLimit(prefetch); + } + + @Override + public Context currentContext() { + return parent.actual.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + parent.innerValue(index, t); + } + + @Override + public void onError(Throwable t) { + parent.innerError(t); + } + + @Override + public void onComplete() { + parent.innerComplete(index); + } + + public void cancel() { + Operators.terminate(S, this); + } + + void requestOne() { + int p = produced + 1; + if (p == limit) { + produced = 0; + s.request(p); + } + else { + produced = p; + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } + + /** + * The queue element type for internal use with FluxCombineLatest. + */ + static final class SourceAndArray { + + final CombineLatestInner source; + final Object[] array; + + SourceAndArray(CombineLatestInner source, Object[] array) { + this.source = source; + this.array = array; + } + + final Stream toStream() { + return Stream.of(this.array); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatArray.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatArray.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatArray.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.util.annotation.Nullable; + +/** + * Concatenates a fixed array of Publishers' values. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxConcatArray extends Flux implements SourceProducer { + + static final Object WORKING = new Object(); + static final Object DONE = new Object(); + + final Publisher[] array; + + final boolean delayError; + + @SafeVarargs + FluxConcatArray(boolean delayError, Publisher... array) { + this.array = Objects.requireNonNull(array, "array"); + this.delayError = delayError; + } + + @Override + public void subscribe(CoreSubscriber actual) { + Publisher[] a = array; + + if (a.length == 0) { + Operators.complete(actual); + return; + } + if (a.length == 1) { + Publisher p = a[0]; + + if (p == null) { + Operators.error(actual, new NullPointerException("The single source Publisher is null")); + } else { + p.subscribe(actual); + } + return; + } + + if (delayError) { + ConcatArrayDelayErrorSubscriber parent = new + ConcatArrayDelayErrorSubscriber<>(actual, a); + + parent.onComplete(); + return; + } + ConcatArraySubscriber parent = new ConcatArraySubscriber<>(actual, a); + + parent.onComplete(); + } + + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + /** + * Returns a new instance which has the additional source to be merged together with + * the current array of sources. + *

+ * This operation doesn't change the current FluxMerge instance. + * + * @param source the new source to merge with the others + * @return the new FluxConcatArray instance + */ + FluxConcatArray concatAdditionalSourceLast(Publisher source) { + int n = array.length; + @SuppressWarnings("unchecked") + Publisher[] newArray = new Publisher[n + 1]; + System.arraycopy(array, 0, newArray, 0, n); + newArray[n] = source; + + return new FluxConcatArray<>(delayError, newArray); + } + + /** + * Returns a new instance which has the additional source to be merged together with + * the current array of sources. + *

+ * This operation doesn't change the current FluxMerge instance. + * + * @param source the new source to merge with the others + * @return the new FluxConcatArray instance + */ + @SuppressWarnings("unchecked") + FluxConcatArray concatAdditionalIgnoredLast(Publisher + source) { + int n = array.length; + Publisher[] newArray = new Publisher[n + 1]; + //noinspection SuspiciousSystemArraycopy + System.arraycopy(array, 0, newArray, 0, n); + newArray[n - 1] = Mono.ignoreElements(newArray[n - 1]); + newArray[n] = source; + + return new FluxConcatArray<>(delayError, newArray); + } + + /** + * Returns a new instance which has the additional first source to be concatenated together with + * the current array of sources. + *

+ * This operation doesn't change the current FluxConcatArray instance. + * + * @param source the new source to merge with the others + * @return the new FluxConcatArray instance + */ + FluxConcatArray concatAdditionalSourceFirst(Publisher source) { + int n = array.length; + @SuppressWarnings("unchecked") + Publisher[] newArray = new Publisher[n + 1]; + System.arraycopy(array, 0, newArray, 1, n); + newArray[0] = source; + + return new FluxConcatArray<>(delayError, newArray); + } + + interface SubscriptionAware { + Subscription upstream(); + } + + static final class ConcatArraySubscriber extends ThreadLocal implements InnerOperator, SubscriptionAware { + + final CoreSubscriber actual; + final Publisher[] sources; + + int index; + long produced; + Subscription s; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ConcatArraySubscriber.class, "requested"); + + volatile boolean cancelled; + + ConcatArraySubscriber(CoreSubscriber actual, Publisher[] sources) { + this.actual = actual; + this.sources = sources; + } + + @Override + public void onSubscribe(Subscription s) { + if (this.cancelled) { + this.remove(); + s.cancel(); + return; + } + + final Subscription previousSubscription = this.s; + + this.s = s; + + if (previousSubscription == null) { + this.actual.onSubscribe(this); + return; + } + + final long actualRequested = activateAndGetRequested(REQUESTED, this); + if (actualRequested > 0) { + s.request(actualRequested); + } + } + + @Override + public void onNext(T t) { + this.produced++; + + this.actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + this.remove(); + this.actual.onError(t); + } + + @Override + public void onComplete() { + if (this.get() == WORKING) { + this.set(DONE); + return; + } + + final Publisher[] a = this.sources; + + for (;;) { + this.set(WORKING); + + int i = this.index; + if (i == a.length) { + this.remove(); + + if (this.cancelled) { + return; + } + + this.actual.onComplete(); + return; + } + + Publisher p = a[i]; + + if (p == null) { + this.remove(); + + if (this.cancelled) { + return; + } + + this.actual.onError(new NullPointerException("Source Publisher at index " + i + " is null")); + return; + } + + long c = this.produced; + if (c != 0L) { + this.produced = 0L; + deactivateAndProduce(c, REQUESTED, this); + } + + this.index = ++i; + + if (this.cancelled) { + return; + } + p.subscribe(this); + + final Object state = this.get(); + if (state != DONE) { + this.remove(); + return; + } + } + } + + @Override + public void request(long n) { + final Subscription subscription = addCapAndGetSubscription(n, REQUESTED, this); + + if (subscription == null) { + return; + } + + subscription.request(n); + } + + @Override + public void cancel() { + this.remove(); + + this.cancelled = true; + + if ((this.requested & Long.MIN_VALUE) != Long.MIN_VALUE) { + this.s.cancel(); + } + } + + @Override + public Subscription upstream() { + return this.s; + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.PARENT) return this.s; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + + return InnerOperator.super.scanUnsafe(key); + } + } + + static final class ConcatArrayDelayErrorSubscriber extends ThreadLocal implements InnerOperator, SubscriptionAware { + + final CoreSubscriber actual; + final Publisher[] sources; + + int index; + long produced; + Subscription s; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ConcatArrayDelayErrorSubscriber.class, "requested"); + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(ConcatArrayDelayErrorSubscriber.class, Throwable.class, "error"); + + volatile boolean cancelled; + + ConcatArrayDelayErrorSubscriber(CoreSubscriber actual, Publisher[] sources) { + this.actual = actual; + this.sources = sources; + } + + @Override + public void onSubscribe(Subscription s) { + if (this.cancelled) { + this.remove(); + s.cancel(); + return; + } + + final Subscription previousSubscription = this.s; + + this.s = s; + + if (previousSubscription == null) { + this.actual.onSubscribe(this); + return; + } + + final long actualRequested = activateAndGetRequested(REQUESTED, this); + if (actualRequested > 0) { + s.request(actualRequested); + } + } + + @Override + public void onNext(T t) { + this.produced++; + + this.actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (!Exceptions.addThrowable(ERROR, this, t)) { + this.remove(); + Operators.onErrorDropped(t, this.actual.currentContext()); + return; + } + + onComplete(); + } + + @Override + public void onComplete() { + if (this.get() == WORKING) { + this.set(DONE); + return; + } + + final Publisher[] a = this.sources; + + for (;;) { + this.set(WORKING); + + int i = this.index; + if (i == a.length) { + this.remove(); + + final Throwable e = Exceptions.terminate(ERROR, this); + if (e == Exceptions.TERMINATED) { + return; + } + + if (e != null) { + this.actual.onError(e); + } else { + this.actual.onComplete(); + } + return; + } + + final Publisher p = a[i]; + + if (p == null) { + this.remove(); + + if (this.cancelled) { + return; + } + + final NullPointerException npe = new NullPointerException("Source Publisher at index " + i + " is null"); + if (!Exceptions.addThrowable(ERROR, this, npe)) { + Operators.onErrorDropped(npe, this.actual.currentContext()); + return; + } + + final Throwable throwable = Exceptions.terminate(ERROR, this); + if (throwable == Exceptions.TERMINATED) { + return; + } + + this.actual.onError(throwable); + return; + } + + long c = this.produced; + if (c != 0L) { + this.produced = 0L; + deactivateAndProduce(c, REQUESTED, this); + } + + this.index = ++i; + + if (this.cancelled) { + return; + } + + p.subscribe(this); + + final Object state = this.get(); + if (state != DONE) { + this.remove(); + return; + } + } + } + + @Override + public void request(long n) { + final Subscription subscription = addCapAndGetSubscription(n, REQUESTED, this); + + if (subscription == null) { + return; + } + + subscription.request(n); + } + + @Override + public void cancel() { + this.remove(); + + this.cancelled = true; + + if ((this.requested & Long.MIN_VALUE) != Long.MIN_VALUE) { + this.s.cancel(); + } + + final Throwable throwable = Exceptions.terminate(ERROR, this); + if (throwable != null) { + Operators.onErrorDropped(throwable, this.actual.currentContext()); + } + } + + @Override + public Subscription upstream() { + return this.s; + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.DELAY_ERROR) return true; + if (key == Attr.TERMINATED) return this.error == Exceptions.TERMINATED; + if (key == Attr.ERROR) return this.error != Exceptions.TERMINATED ? this.error : null; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.PARENT) return this.s; + if (key == Attr.CANCELLED) return this.cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return this.requested; + + return InnerOperator.super.scanUnsafe(key); + } + } + + static long activateAndGetRequested(AtomicLongFieldUpdater updater, T instance) { + for (;;) { + final long deactivatedRequested = updater.get(instance); + final long actualRequested = deactivatedRequested & Long.MAX_VALUE; + + if (updater.compareAndSet(instance, deactivatedRequested, actualRequested)) { + return actualRequested; + } + } + } + + static void deactivateAndProduce(long produced, AtomicLongFieldUpdater updater, T instance) { + for (;;) { + final long actualRequested = updater.get(instance); + final long deactivatedRequested = actualRequested == Long.MAX_VALUE + ? Long.MAX_VALUE | Long.MIN_VALUE + : (actualRequested - produced) | Long.MIN_VALUE; + + if (updater.compareAndSet(instance, actualRequested, deactivatedRequested)) { + return; + } + } + } + + @Nullable + static Subscription addCapAndGetSubscription(long n, AtomicLongFieldUpdater updater, T instance) { + for (;;) { + final long state = updater.get(instance); + final Subscription s = instance.upstream(); + final long actualRequested = state & Long.MAX_VALUE; + final long status = state & Long.MIN_VALUE; + + if (actualRequested == Long.MAX_VALUE) { + return status == Long.MIN_VALUE ? null : s; + } + + if (updater.compareAndSet(instance, state, Operators.addCap(actualRequested , n) | status)) { + return status == Long.MIN_VALUE ? null : s; + } + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatIterable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatIterable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatIterable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; + +/** + * Concatenates a fixed array of Publishers' values. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxConcatIterable extends Flux implements SourceProducer { + + final Iterable> iterable; + + FluxConcatIterable(Iterable> iterable) { + this.iterable = Objects.requireNonNull(iterable, "iterable"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + + Iterator> it; + + try { + it = Objects.requireNonNull(iterable.iterator(), + "The Iterator returned is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + + ConcatIterableSubscriber parent = new ConcatIterableSubscriber<>(actual, it); + + actual.onSubscribe(parent); + + if (!parent.isCancelled()) { + parent.onComplete(); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class ConcatIterableSubscriber + extends Operators.MultiSubscriptionSubscriber { + + final Iterator> it; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ConcatIterableSubscriber.class, + "wip"); + + long produced; + + ConcatIterableSubscriber(CoreSubscriber actual, + Iterator> it) { + super(actual); + this.it = it; + } + + @Override + public void onNext(T t) { + produced++; + + actual.onNext(t); + } + + @Override + public void onComplete() { + if (WIP.getAndIncrement(this) == 0) { + Iterator> a = this.it; + do { + if (isCancelled()) { + return; + } + + boolean b; + + try { + b = a.hasNext(); + } + catch (Throwable e) { + onError(Operators.onOperatorError(this, e, + actual.currentContext())); + return; + } + + if (isCancelled()) { + return; + } + + if (!b) { + actual.onComplete(); + return; + } + + Publisher p; + + try { + p = Objects.requireNonNull(it.next(), + "The Publisher returned by the iterator is null"); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(this, e, + actual.currentContext())); + return; + } + + if (isCancelled()) { + return; + } + + long c = produced; + if (c != 0L) { + produced = 0L; + produced(c); + } + + p.subscribe(this); + + if (isCancelled()) { + return; + } + + } + while (WIP.decrementAndGet(this) != 0); + } + + } + + @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/FluxConcatMap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatMap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatMap.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,888 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +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 static reactor.core.Exceptions.TERMINATED; + +/** + * Maps each upstream value into a Publisher and concatenates them into one + * sequence of items. + * + * @param the source value type + * @param the output value type + * + * @see Reactive-Streams-Commons + */ +final class FluxConcatMap extends InternalFluxOperator { + + final Function> mapper; + + final Supplier> queueSupplier; + + final int prefetch; + + final ErrorMode errorMode; + + /** + * Indicates when an error from the main source should be reported. + */ + enum ErrorMode { + /** + * Report the error immediately, cancelling the active inner source. + */ + IMMEDIATE, /** + * Report error after an inner source terminated. + */ + BOUNDARY, /** + * Report the error after all sources terminated. + */ + END + } + + static CoreSubscriber subscriber(CoreSubscriber s, + Function> mapper, + Supplier> queueSupplier, + int prefetch, ErrorMode errorMode) { + switch (errorMode) { + case BOUNDARY: + return new ConcatMapDelayed<>(s, + mapper, + queueSupplier, + prefetch, + false); + case END: + return new ConcatMapDelayed<>(s, + mapper, + queueSupplier, + prefetch, + true); + default: + return new ConcatMapImmediate<>(s, mapper, queueSupplier, prefetch); + } + } + + FluxConcatMap(Flux source, + Function> mapper, + Supplier> queueSupplier, + int prefetch, + ErrorMode errorMode) { + super(source); + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.mapper = Objects.requireNonNull(mapper, "mapper"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + this.prefetch = prefetch; + this.errorMode = Objects.requireNonNull(errorMode, "errorMode"); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (FluxFlatMap.trySubscribeScalarMap(source, actual, mapper, false, true)) { + return null; + } + + return subscriber(actual, mapper, queueSupplier, prefetch, errorMode); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ConcatMapImmediate + implements FluxConcatMapSupport { + + final CoreSubscriber actual; + final Context ctx; + + final ConcatMapInner inner; + + final Function> mapper; + + final Supplier> queueSupplier; + + final int prefetch; + + final int limit; + + Subscription s; + + int consumed; + + volatile Queue queue; + + volatile boolean done; + + volatile boolean cancelled; + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(ConcatMapImmediate.class, + Throwable.class, + "error"); + + volatile boolean active; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ConcatMapImmediate.class, "wip"); + + volatile int guard; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater GUARD = + AtomicIntegerFieldUpdater.newUpdater(ConcatMapImmediate.class, "guard"); + + int sourceMode; + + ConcatMapImmediate(CoreSubscriber actual, + Function> mapper, + Supplier> queueSupplier, int prefetch) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.mapper = mapper; + this.queueSupplier = queueSupplier; + this.prefetch = prefetch; + this.limit = Operators.unboundedOrLimit(prefetch); + this.inner = new ConcatMapInner<>(this); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done || error == TERMINATED; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return FluxConcatMapSupport.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + if (s instanceof Fuseable.QueueSubscription) { + @SuppressWarnings("unchecked") Fuseable.QueueSubscription f = + (Fuseable.QueueSubscription) s; + int m = f.requestFusion(Fuseable.ANY | Fuseable.THREAD_BARRIER); + if (m == Fuseable.SYNC) { + sourceMode = Fuseable.SYNC; + queue = f; + done = true; + + actual.onSubscribe(this); + + drain(); + return; + } + else if (m == Fuseable.ASYNC) { + sourceMode = Fuseable.ASYNC; + queue = f; + } + else { + queue = queueSupplier.get(); + } + } + else { + queue = queueSupplier.get(); + } + + actual.onSubscribe(this); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == Fuseable.ASYNC) { + drain(); + } + else if (!queue.offer(t)) { + onError(Operators.onOperatorError(s, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t, + this.ctx)); + Operators.onDiscard(t, this.ctx); + } + else { + drain(); + } + } + + @Override + public void onError(Throwable t) { + if (Exceptions.addThrowable(ERROR, this, t)) { + inner.cancel(); + + if (GUARD.getAndIncrement(this) == 0) { + t = Exceptions.terminate(ERROR, this); + if (t != TERMINATED) { + actual.onError(t); + Operators.onDiscardQueueWithClear(queue, this.ctx, null); + } + } + } + else { + Operators.onErrorDropped(t, this.ctx); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void innerNext(R value) { + if (guard == 0 && GUARD.compareAndSet(this, 0, 1)) { + actual.onNext(value); + if (GUARD.compareAndSet(this, 1, 0)) { + return; + } + Throwable e = Exceptions.terminate(ERROR, this); + if (e != TERMINATED) { + actual.onError(e); + } + } + } + + @Override + public void innerComplete() { + active = false; + drain(); + } + + @Override + public void innerError(Throwable e) { + e = Operators.onNextInnerError(e, currentContext(), s); + if(e != null) { + if (Exceptions.addThrowable(ERROR, this, e)) { + s.cancel(); + + if (GUARD.getAndIncrement(this) == 0) { + e = Exceptions.terminate(ERROR, this); + if (e != TERMINATED) { + actual.onError(e); + } + } + } + else { + Operators.onErrorDropped(e, this.ctx); + } + } + else { + active = false; + drain(); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + inner.request(n); + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + inner.cancel(); + s.cancel(); + Operators.onDiscardQueueWithClear(queue, this.ctx, null); + } + } + + void drain() { + if (WIP.getAndIncrement(this) == 0) { + for (; ; ) { + if (cancelled) { + return; + } + + if (!active) { + boolean d = done; + + T v; + + try { + v = queue.poll(); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(s, e, this.ctx)); + return; + } + + boolean empty = v == null; + + if (d && empty) { + actual.onComplete(); + return; + } + + if (!empty) { + Publisher p; + + try { + p = Objects.requireNonNull(mapper.apply(v), + "The mapper returned a null Publisher"); + } + catch (Throwable e) { + Operators.onDiscard(v, this.ctx); + Throwable e_ = Operators.onNextError(v, e, this.ctx, s); + if (e_ != null) { + actual.onError(Operators.onOperatorError(s, e, v, + this.ctx)); + return; + } + else { + continue; + } + } + + if (sourceMode != Fuseable.SYNC) { + int c = consumed + 1; + if (c == limit) { + consumed = 0; + s.request(c); + } + else { + consumed = c; + } + } + + if (p instanceof Callable) { + @SuppressWarnings("unchecked") Callable callable = + (Callable) p; + + R vr; + + try { + vr = callable.call(); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(v, e, this.ctx, s); + if (e_ != null) { + actual.onError(Operators.onOperatorError(s, e, v, + this.ctx)); + return; + } + else { + continue; + } + } + + if (vr == null) { + continue; + } + + if (inner.isUnbounded()) { + if (guard == 0 && GUARD.compareAndSet(this, 0, 1)) { + actual.onNext(vr); + if (!GUARD.compareAndSet(this, 1, 0)) { + Throwable e = + Exceptions.terminate(ERROR, this); + if (e != TERMINATED) { + actual.onError(e); + } + return; + } + } + continue; + } + else { + active = true; + inner.set(new WeakScalarSubscription<>(vr, inner)); + } + + } + else { + active = true; + p.subscribe(inner); + } + } + } + if (WIP.decrementAndGet(this) == 0) { + break; + } + } + } + } + } + + static final class WeakScalarSubscription implements Subscription { + + final CoreSubscriber actual; + final T value; + boolean once; + + WeakScalarSubscription(T value, CoreSubscriber actual) { + this.value = value; + this.actual = actual; + } + + @Override + public void request(long n) { + if (n > 0 && !once) { + once = true; + Subscriber a = actual; + a.onNext(value); + a.onComplete(); + } + } + + @Override + public void cancel() { + Operators.onDiscard(value, actual.currentContext()); + } + } + + static final class ConcatMapDelayed + implements FluxConcatMapSupport { + + final CoreSubscriber actual; + + final ConcatMapInner inner; + + final Function> mapper; + + final Supplier> queueSupplier; + + final int prefetch; + + final int limit; + + final boolean veryEnd; + + Subscription s; + + int consumed; + + volatile Queue queue; + + volatile boolean done; + + volatile boolean cancelled; + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(ConcatMapDelayed.class, + Throwable.class, + "error"); + + volatile boolean active; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ConcatMapDelayed.class, "wip"); + + int sourceMode; + + ConcatMapDelayed(CoreSubscriber actual, + Function> mapper, + Supplier> queueSupplier, + int prefetch, boolean veryEnd) { + this.actual = actual; + this.mapper = mapper; + this.queueSupplier = queueSupplier; + this.prefetch = prefetch; + this.limit = Operators.unboundedOrLimit(prefetch); + this.veryEnd = veryEnd; + this.inner = new ConcatMapInner<>(this); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.ERROR) return error; + if (key == Attr.DELAY_ERROR) return true; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return FluxConcatMapSupport.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + if (s instanceof Fuseable.QueueSubscription) { + @SuppressWarnings("unchecked") Fuseable.QueueSubscription f = + (Fuseable.QueueSubscription) s; + + int m = f.requestFusion(Fuseable.ANY | Fuseable.THREAD_BARRIER); + + if (m == Fuseable.SYNC) { + sourceMode = Fuseable.SYNC; + queue = f; + done = true; + + actual.onSubscribe(this); + + drain(); + return; + } + else if (m == Fuseable.ASYNC) { + sourceMode = Fuseable.ASYNC; + queue = f; + } + else { + queue = queueSupplier.get(); + } + } + else { + queue = queueSupplier.get(); + } + + actual.onSubscribe(this); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == Fuseable.ASYNC) { + drain(); + } + else if (!queue.offer(t)) { + Context ctx = actual.currentContext(); + onError(Operators.onOperatorError(s, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t, + ctx)); + Operators.onDiscard(t, ctx); + } + else { + drain(); + } + } + + @Override + public void onError(Throwable t) { + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(); + } + else { + Operators.onErrorDropped(t, actual.currentContext()); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void innerNext(R value) { + actual.onNext(value); + } + + @Override + public void innerComplete() { + active = false; + drain(); + } + + @Override + public void innerError(Throwable e) { + e = Operators.onNextInnerError(e, currentContext(), s); + if(e != null) { + if (Exceptions.addThrowable(ERROR, this, e)) { + if (!veryEnd) { + s.cancel(); + done = true; + } + active = false; + drain(); + } + else { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + else { + active = false; + } + } + + @Override + public void request(long n) { + inner.request(n); + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + inner.cancel(); + s.cancel(); + Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); + } + } + + void drain() { + if (WIP.getAndIncrement(this) == 0) { + Context ctx = null; + for (; ; ) { + if (cancelled) { + return; + } + + if (!active) { + + boolean d = done; + + if (d && !veryEnd) { + Throwable ex = error; + if (ex != null) { + ex = Exceptions.terminate(ERROR, this); + if (ex != TERMINATED) { + actual.onError(ex); + } + return; + } + } + + T v; + + try { + v = queue.poll(); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + + boolean empty = v == null; + + if (d && empty) { + Throwable ex = Exceptions.terminate(ERROR, this); + if (ex != null && ex != TERMINATED) { + actual.onError(ex); + } + else { + actual.onComplete(); + } + return; + } + + if (!empty) { + Publisher p; + + try { + p = Objects.requireNonNull(mapper.apply(v), + "The mapper returned a null Publisher"); + } + catch (Throwable e) { + if (ctx == null) { + ctx = actual.currentContext(); + } + Operators.onDiscard(v, ctx); + Throwable e_ = Operators.onNextError(v, e, ctx, s); + if (e_ != null) { + actual.onError(Operators.onOperatorError(s, e, v, ctx)); + return; + } + else { + continue; + } + } + + if (sourceMode != Fuseable.SYNC) { + int c = consumed + 1; + if (c == limit) { + consumed = 0; + s.request(c); + } + else { + consumed = c; + } + } + + if (p instanceof Callable) { + @SuppressWarnings("unchecked") Callable supplier = + (Callable) p; + + R vr; + + try { + vr = supplier.call(); + } + catch (Throwable e) { + //does the strategy apply? if so, short-circuit the delayError. In any case, don't cancel + if (ctx == null) { + ctx = actual.currentContext(); + } + Throwable e_ = Operators.onNextError(v, e, ctx); + if (e_ == null) { + continue; + } + //now if error mode strategy doesn't apply, let delayError play + if (veryEnd && Exceptions.addThrowable(ERROR, this, e_)) { + continue; + } + else { + actual.onError(Operators.onOperatorError(s, e_, v, ctx)); + return; + } + } + + if (vr == null) { + continue; + } + + if (inner.isUnbounded()) { + actual.onNext(vr); + continue; + } + else { + active = true; + inner.set(new WeakScalarSubscription<>(vr, inner)); + } + } + else { + active = true; + p.subscribe(inner); + } + } + } + if (WIP.decrementAndGet(this) == 0) { + break; + } + } + } + } + } + + /** + * @param input type consumed by the InnerOperator + * @param output type, as forwarded by the inner this helper supports + */ + interface FluxConcatMapSupport extends InnerOperator { + + void innerNext(T value); + + void innerComplete(); + + void innerError(Throwable e); + } + + static final class ConcatMapInner + extends Operators.MultiSubscriptionSubscriber { + + final FluxConcatMapSupport parent; + + long produced; + + ConcatMapInner(FluxConcatMapSupport parent) { + super(Operators.emptySubscriber()); + this.parent = parent; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) return parent; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public void onNext(R t) { + produced++; + + parent.innerNext(t); + } + + @Override + public void onError(Throwable t) { + long p = produced; + + if (p != 0L) { + produced = 0L; + produced(p); + } + + parent.innerError(t); + } + + @Override + public void onComplete() { + long p = produced; + + if (p != 0L) { + produced = 0L; + produced(p); + } + + parent.innerComplete(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatMapNoPrefetch.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatMapNoPrefetch.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxConcatMapNoPrefetch.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2020-2021 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.Callable; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.publisher.FluxConcatMap.ConcatMapInner; +import reactor.core.publisher.FluxConcatMap.ErrorMode; +import reactor.core.publisher.FluxConcatMap.FluxConcatMapSupport; +import reactor.core.publisher.FluxConcatMap.WeakScalarSubscription; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Maps each upstream value into a Publisher and concatenates them into one + * sequence of items. + * + * @param the source value type + * @param the output value type + * + * @see FluxConcatMap + */ +final class FluxConcatMapNoPrefetch extends InternalFluxOperator { + + final Function> mapper; + + final ErrorMode errorMode; + + FluxConcatMapNoPrefetch( + Flux source, + Function> mapper, + ErrorMode errorMode + ) { + super(source); + this.mapper = Objects.requireNonNull(mapper, "mapper"); + this.errorMode = errorMode; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (FluxFlatMap.trySubscribeScalarMap(source, actual, mapper, false, true)) { + return null; + } + + return new FluxConcatMapNoPrefetchSubscriber<>(actual, mapper, errorMode); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + @Override + public int getPrefetch() { + return 0; + } + + static final class FluxConcatMapNoPrefetchSubscriber implements FluxConcatMapSupport { + + enum State { + INITIAL, + /** + * Requested from {@link #upstream}, waiting for {@link #onNext(Object)} + */ + REQUESTED, + /** + * {@link #onNext(Object)} received, listening on {@link #inner} + */ + ACTIVE, + /** + * Received outer {@link #onComplete()}, waiting for {@link #inner} to complete + */ + LAST_ACTIVE, + /** + * Terminated either successfully or after an error + */ + TERMINATED, + CANCELLED, + } + + volatile State state; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater STATE = AtomicReferenceFieldUpdater.newUpdater( + FluxConcatMapNoPrefetchSubscriber.class, + State.class, + "state" + ); + + volatile Throwable error; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = AtomicReferenceFieldUpdater.newUpdater( + FluxConcatMapNoPrefetchSubscriber.class, + Throwable.class, + "error" + ); + + final CoreSubscriber actual; + + final ConcatMapInner inner; + + final Function> mapper; + + final ErrorMode errorMode; + + Subscription upstream; + + FluxConcatMapNoPrefetchSubscriber( + CoreSubscriber actual, + Function> mapper, + ErrorMode errorMode + ) { + this.actual = actual; + this.mapper = mapper; + this.errorMode = errorMode; + this.inner = new ConcatMapInner<>(this); + STATE.lazySet(this, State.INITIAL); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return upstream; + if (key == Attr.TERMINATED) return state == State.TERMINATED; + if (key == Attr.CANCELLED) return state == State.CANCELLED; + if (key == Attr.DELAY_ERROR) return this.errorMode != ErrorMode.IMMEDIATE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return FluxConcatMapSupport.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.upstream, s)) { + this.upstream = s; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!STATE.compareAndSet(this, State.REQUESTED, State.ACTIVE)) { + switch (state) { + case CANCELLED: + Operators.onDiscard(t, currentContext()); + break; + case TERMINATED: + Operators.onNextDropped(t, currentContext()); + break; + } + return; + } + + try { + Publisher p = mapper.apply(t); + Objects.requireNonNull(p, "The mapper returned a null Publisher"); + + if (p instanceof Callable) { + @SuppressWarnings("unchecked") + Callable callable = (Callable) p; + + R result = callable.call(); + if (result == null) { + innerComplete(); + return; + } + + if (inner.isUnbounded()) { + actual.onNext(result); + innerComplete(); + return; + } + + inner.set(new WeakScalarSubscription<>(result, inner)); + return; + } + + p.subscribe(inner); + } + catch (Throwable e) { + Context ctx = actual.currentContext(); + Operators.onDiscard(t, ctx); + if (!maybeOnError(Operators.onNextError(t, e, ctx), ctx, upstream)) { + innerComplete(); + } + } + } + + @Override + public void onError(Throwable t) { + Context ctx = currentContext(); + if (!maybeOnError(t, ctx, inner)) { + onComplete(); + } + } + + @Override + public void onComplete() { + for (State previousState = this.state; ; previousState = this.state) { + switch (previousState) { + case INITIAL: + case REQUESTED: + if (!STATE.compareAndSet(this, previousState, State.TERMINATED)) { + continue; + } + + Throwable ex = error; + if (ex != null) { + actual.onError(ex); + return; + } + actual.onComplete(); + return; + case ACTIVE: + if (!STATE.compareAndSet(this, previousState, State.LAST_ACTIVE)) { + continue; + } + return; + default: + return; + } + } + } + + @Override + public synchronized void innerNext(R value) { + switch (state) { + case ACTIVE: + case LAST_ACTIVE: + actual.onNext(value); + break; + default: + Operators.onDiscard(value, currentContext()); + break; + } + } + + @Override + public void innerComplete() { + for (State previousState = this.state; ; previousState = this.state) { + switch (previousState) { + case ACTIVE: + if (!STATE.compareAndSet(this, previousState, State.REQUESTED)) { + continue; + } + upstream.request(1); + return; + case LAST_ACTIVE: + if (!STATE.compareAndSet(this, previousState, State.TERMINATED)) { + continue; + } + + Throwable ex = error; + if (ex != null) { + actual.onError(ex); + return; + } + actual.onComplete(); + return; + default: + return; + } + } + } + + @Override + public void innerError(Throwable e) { + Context ctx = currentContext(); + if (!maybeOnError(Operators.onNextInnerError(e, ctx, null), ctx, upstream)) { + innerComplete(); + } + } + + private boolean maybeOnError(@Nullable Throwable e, Context ctx, Subscription subscriptionToCancel) { + if (e == null) { + return false; + } + + if (!ERROR.compareAndSet(this, null, e)) { + Operators.onErrorDropped(e, ctx); + } + + if (errorMode == ErrorMode.END) { + return false; + } + + for (State previousState = this.state; ; previousState = this.state) { + switch (previousState) { + case CANCELLED: + case TERMINATED: + return true; + default: + if (!STATE.compareAndSet(this, previousState, State.TERMINATED)) { + continue; + } + subscriptionToCancel.cancel(); + synchronized (this) { + actual.onError(error); + } + return true; + } + } + } + + @Override + public void request(long n) { + if (STATE.compareAndSet(this, State.INITIAL, State.REQUESTED)) { + upstream.request(1); + } + inner.request(n); + } + + @Override + public void cancel() { + switch (STATE.getAndSet(this, State.CANCELLED)) { + case CANCELLED: + break; + case TERMINATED: + inner.cancel(); + break; + default: + inner.cancel(); + upstream.cancel(); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxContextWrite.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxContextWrite.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxContextWrite.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +final class FluxContextWrite extends InternalFluxOperator implements Fuseable { + + final Function doOnContext; + + FluxContextWrite(Flux source, + Function doOnContext) { + super(source); + this.doOnContext = Objects.requireNonNull(doOnContext, "doOnContext"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + Context c = doOnContext.apply(actual.currentContext()); + + return new ContextWriteSubscriber<>(actual, c); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Scannable.Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ContextWriteSubscriber + implements ConditionalSubscriber, InnerOperator, + QueueSubscription { + + final CoreSubscriber actual; + final ConditionalSubscriber actualConditional; + final Context context; + + QueueSubscription qs; + Subscription s; + + @SuppressWarnings("unchecked") + ContextWriteSubscriber(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 == Scannable.Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Context currentContext() { + return this.context; + } + + @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); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public boolean tryOnNext(T t) { + if (actualConditional != null) { + return actualConditional.tryOnNext(t); + } + actual.onNext(t); + return true; + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public int requestFusion(int requestedMode) { + if (qs == null) { + return Fuseable.NONE; + } + return qs.requestFusion(requestedMode); + } + + @Override + @Nullable + public T poll() { + if (qs != null) { + return qs.poll(); + } + return null; + } + + @Override + public boolean isEmpty() { + return qs == null || qs.isEmpty(); + } + + @Override + public void clear() { + if (qs != null) { + qs.clear(); + } + } + + @Override + public int size() { + return qs != null ? qs.size() : 0; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxCreate.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxCreate.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxCreate.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,1043 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscriber; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.publisher.FluxSink.OverflowStrategy; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * Provides a multi-valued sink API for a callback that is called for each individual + * Subscriber. + * + * @param the value type + */ +final class FluxCreate extends Flux implements SourceProducer { + + enum CreateMode { + PUSH_ONLY, PUSH_PULL + } + + final Consumer> source; + + final OverflowStrategy backpressure; + + final CreateMode createMode; + + FluxCreate(Consumer> source, + FluxSink.OverflowStrategy backpressure, + CreateMode createMode) { + this.source = Objects.requireNonNull(source, "source"); + this.backpressure = Objects.requireNonNull(backpressure, "backpressure"); + this.createMode = createMode; + } + + static BaseSink createSink(CoreSubscriber t, + OverflowStrategy backpressure) { + switch (backpressure) { + case IGNORE: { + return new IgnoreSink<>(t); + } + case ERROR: { + return new ErrorAsyncSink<>(t); + } + case DROP: { + return new DropAsyncSink<>(t); + } + case LATEST: { + return new LatestAsyncSink<>(t); + } + default: { + return new BufferAsyncSink<>(t, Queues.SMALL_BUFFER_SIZE); + } + } + } + + @Override + public void subscribe(CoreSubscriber actual) { + BaseSink sink = createSink(actual, backpressure); + + actual.onSubscribe(sink); + try { + source.accept( + createMode == CreateMode.PUSH_PULL ? new SerializedFluxSink<>(sink) : + sink); + } + catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + sink.error(Operators.onOperatorError(ex, actual.currentContext())); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + return null; + } + + /** + * Serializes calls to onNext, onError and onComplete. + * + * @param the value type + */ + static final class SerializedFluxSink implements FluxSink, Scannable { + + final BaseSink sink; + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(SerializedFluxSink.class, + Throwable.class, + "error"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(SerializedFluxSink.class, "wip"); + + final Queue mpscQueue; + + volatile boolean done; + + SerializedFluxSink(BaseSink sink) { + this.sink = sink; + this.mpscQueue = Queues.unboundedMultiproducer().get(); + } + + @Override + public Context currentContext() { + return sink.currentContext(); + } + + @Override + public FluxSink next(T t) { + Objects.requireNonNull(t, "t is null in sink.next(t)"); + if (sink.isTerminated() || done) { + Operators.onNextDropped(t, sink.currentContext()); + return this; + } + if (WIP.get(this) == 0 && WIP.compareAndSet(this, 0, 1)) { + try { + sink.next(t); + } + catch (Throwable ex) { + Operators.onOperatorError(sink, ex, t, sink.currentContext()); + } + if (WIP.decrementAndGet(this) == 0) { + return this; + } + } + else { + this.mpscQueue.offer(t); + if (WIP.getAndIncrement(this) != 0) { + return this; + } + } + drainLoop(); + return this; + } + + @Override + public void error(Throwable t) { + Objects.requireNonNull(t, "t is null in sink.error(t)"); + if (sink.isTerminated() || done) { + Operators.onOperatorError(t, sink.currentContext()); + return; + } + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(); + } + else { + Context ctx = sink.currentContext(); + Operators.onDiscardQueueWithClear(mpscQueue, ctx, null); + Operators.onOperatorError(t, ctx); + } + } + + @Override + public void complete() { + if (sink.isTerminated() || done) { + return; + } + done = true; + drain(); + } + + //impl note: don't use sink.isTerminated() in the drain loop, + //it needs to separately check its own `done` status before calling the base sink + //complete()/error() methods (which do flip the isTerminated), otherwise it could + //bypass the terminate handler (in buffer and latest variants notably). + void drain() { + if (WIP.getAndIncrement(this) == 0) { + drainLoop(); + } + } + + void drainLoop() { + Context ctx = sink.currentContext(); + BaseSink e = sink; + Queue q = mpscQueue; + for (; ; ) { + + for (; ; ) { + if (e.isCancelled()) { + Operators.onDiscardQueueWithClear(q, ctx, null); + if (WIP.decrementAndGet(this) == 0) { + return; + } + else { + continue; + } + } + + if (ERROR.get(this) != null) { + Operators.onDiscardQueueWithClear(q, ctx, null); + //noinspection ConstantConditions + e.error(Exceptions.terminate(ERROR, this)); + return; + } + + boolean d = done; + T v = q.poll(); + + boolean empty = v == null; + + if (d && empty) { + e.complete(); + return; + } + + if (empty) { + break; + } + + try { + e.next(v); + } + catch (Throwable ex) { + Operators.onOperatorError(sink, ex, v, sink.currentContext()); + } + } + + if (WIP.decrementAndGet(this) == 0) { + break; + } + } + } + + @Override + public FluxSink onRequest(LongConsumer consumer) { + sink.onRequest(consumer, consumer, sink.requested); + return this; + } + + @Override + public FluxSink onCancel(Disposable d) { + sink.onCancel(d); + return this; + } + + @Override + public FluxSink onDispose(Disposable d) { + sink.onDispose(d); + return this; + } + + @Override + public long requestedFromDownstream() { + return sink.requestedFromDownstream(); + } + + @Override + public boolean isCancelled() { + return sink.isCancelled(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.BUFFERED) { + return mpscQueue.size(); + } + if (key == Attr.ERROR) { + return error; + } + if (key == Attr.TERMINATED) { + return done; + } + + return sink.scanUnsafe(key); + } + + @Override + public String toString() { + return sink.toString(); + } + } + + /** + * Serializes calls to onNext, onError and onComplete if onRequest is invoked. + * Otherwise, non-serialized base sink is used. + * + * @param the value type + */ + static class SerializeOnRequestSink implements FluxSink, Scannable { + + final BaseSink baseSink; + SerializedFluxSink serializedSink; + FluxSink sink; + + SerializeOnRequestSink(BaseSink sink) { + this.baseSink = sink; + this.sink = sink; + } + + @Override + public Context currentContext() { + return sink.currentContext(); + } + + @Override + public Object scanUnsafe(Attr key) { + return serializedSink != null ? serializedSink.scanUnsafe(key) : + baseSink.scanUnsafe(key); + } + + @Override + public void complete() { + sink.complete(); + } + + @Override + public void error(Throwable e) { + sink.error(e); + } + + @Override + public FluxSink next(T t) { + sink.next(t); + return serializedSink == null ? this : serializedSink; + } + + @Override + public long requestedFromDownstream() { + return sink.requestedFromDownstream(); + } + + @Override + public boolean isCancelled() { + return sink.isCancelled(); + } + + @Override + public FluxSink onRequest(LongConsumer consumer) { + if (serializedSink == null) { + serializedSink = new SerializedFluxSink<>(baseSink); + sink = serializedSink; + } + return sink.onRequest(consumer); + } + + @Override + public FluxSink onCancel(Disposable d) { + sink.onCancel(d); + return sink; + } + + @Override + public FluxSink onDispose(Disposable d) { + sink.onDispose(d); + return this; + } + + @Override + public String toString() { + return baseSink.toString(); + } + } + + static abstract class BaseSink extends AtomicBoolean + implements FluxSink, InnerProducer { + + static final Disposable TERMINATED = OperatorDisposables.DISPOSED; + static final Disposable CANCELLED = Disposables.disposed(); + + final CoreSubscriber actual; + final Context ctx; + + volatile Disposable disposable; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater DISPOSABLE = + AtomicReferenceFieldUpdater.newUpdater(BaseSink.class, + Disposable.class, + "disposable"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(BaseSink.class, "requested"); + + volatile LongConsumer requestConsumer; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + REQUEST_CONSUMER = AtomicReferenceFieldUpdater.newUpdater(BaseSink.class, + LongConsumer.class, + "requestConsumer"); + + BaseSink(CoreSubscriber actual) { + this.actual = actual; + this.ctx = actual.currentContext(); + } + + @Override + 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 void complete() { + if (isTerminated()) { + return; + } + try { + actual.onComplete(); + } + finally { + disposeResource(false); + } + } + + @Override + public void error(Throwable e) { + if (isTerminated()) { + Operators.onOperatorError(e, ctx); + return; + } + try { + actual.onError(e); + } + finally { + disposeResource(false); + } + } + + @Override + public final void cancel() { + disposeResource(true); + onCancel(); + } + + void disposeResource(boolean isCancel) { + Disposable disposed = isCancel ? CANCELLED : TERMINATED; + Disposable d = disposable; + if (d != TERMINATED && d != CANCELLED) { + d = DISPOSABLE.getAndSet(this, disposed); + if (d != null && d != TERMINATED && d != CANCELLED) { + if (isCancel && d instanceof SinkDisposable) { + ((SinkDisposable) d).cancel(); + } + d.dispose(); + } + } + } + + @Override + public long requestedFromDownstream() { + return requested; + } + + void onCancel() { + // default is no-op + } + + @Override + public final boolean isCancelled() { + return disposable == CANCELLED; + } + + final boolean isTerminated() { + return disposable == TERMINATED; + } + + @Override + public final void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + + LongConsumer consumer = requestConsumer; + if (n > 0 && consumer != null && !isCancelled()) { + consumer.accept(n); + } + onRequestedFromDownstream(); + } + } + + void onRequestedFromDownstream() { + // default is no-op + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public FluxSink onRequest(LongConsumer consumer) { + Objects.requireNonNull(consumer, "onRequest"); + onRequest(consumer, n -> { + }, Long.MAX_VALUE); + return this; + } + + protected void onRequest(LongConsumer initialRequestConsumer, + LongConsumer requestConsumer, + long value) { + 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); + } + } + + @Override + public final FluxSink onCancel(Disposable d) { + Objects.requireNonNull(d, "onCancel"); + SinkDisposable sd = new SinkDisposable(null, d); + if (!DISPOSABLE.compareAndSet(this, null, sd)) { + Disposable c = disposable; + if (c == CANCELLED) { + d.dispose(); + } + else if (c instanceof SinkDisposable) { + SinkDisposable current = (SinkDisposable) c; + if (current.onCancel == null) { + current.onCancel = d; + } + else { + d.dispose(); + } + } + } + return this; + } + + @Override + public final FluxSink onDispose(Disposable d) { + Objects.requireNonNull(d, "onDispose"); + SinkDisposable sd = new SinkDisposable(d, null); + if (!DISPOSABLE.compareAndSet(this, null, sd)) { + Disposable c = disposable; + if (c == TERMINATED || c == CANCELLED) { + d.dispose(); + } + else if (c instanceof SinkDisposable) { + SinkDisposable current = (SinkDisposable) c; + if (current.disposable == null) { + current.disposable = d; + } + else { + d.dispose(); + } + } + } + return this; + } + + @Override + @Nullable + 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.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public String toString() { + return "FluxSink"; + } + } + + static final class IgnoreSink extends BaseSink { + + IgnoreSink(CoreSubscriber actual) { + super(actual); + } + + @Override + public FluxSink next(T t) { + if (isTerminated()) { + Operators.onNextDropped(t, ctx); + return this; + } + if (isCancelled()) { + Operators.onDiscard(t, ctx); + return this; + } + + actual.onNext(t); + + for (; ; ) { + long r = requested; + if (r == 0L || REQUESTED.compareAndSet(this, r, r - 1)) { + return this; + } + } + } + + @Override + public String toString() { + return "FluxSink(" + OverflowStrategy.IGNORE + ")"; + } + } + + static abstract class NoOverflowBaseAsyncSink extends BaseSink { + + NoOverflowBaseAsyncSink(CoreSubscriber actual) { + super(actual); + } + + @Override + public final FluxSink next(T t) { + if (isTerminated()) { + Operators.onNextDropped(t, ctx); + return this; + } + + if (requested != 0) { + actual.onNext(t); + Operators.produced(REQUESTED, this, 1); + } + else { + onOverflow(); + Operators.onDiscard(t, ctx); + } + return this; + } + + abstract void onOverflow(); + } + + static final class DropAsyncSink extends NoOverflowBaseAsyncSink { + + DropAsyncSink(CoreSubscriber actual) { + super(actual); + } + + @Override + void onOverflow() { + // nothing to do + } + + @Override + public String toString() { + return "FluxSink(" + OverflowStrategy.DROP + ")"; + } + + } + + static final class ErrorAsyncSink extends NoOverflowBaseAsyncSink { + + ErrorAsyncSink(CoreSubscriber actual) { + super(actual); + } + + @Override + void onOverflow() { + error(Exceptions.failWithOverflow()); + } + + + @Override + public String toString() { + return "FluxSink(" + OverflowStrategy.ERROR + ")"; + } + + } + + static final class BufferAsyncSink extends BaseSink { + + final Queue queue; + + Throwable error; + volatile boolean done; //done is still useful to be able to drain before the terminated handler is executed + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(BufferAsyncSink.class, "wip"); + + BufferAsyncSink(CoreSubscriber actual, int capacityHint) { + super(actual); + this.queue = Queues.unbounded(capacityHint).get(); + } + + @Override + public FluxSink next(T t) { + queue.offer(t); + drain(); + return this; + } + + @Override + public void error(Throwable e) { + error = e; + done = true; + drain(); + } + + @Override + public void complete() { + done = true; + drain(); + } + + @Override + void onRequestedFromDownstream() { + drain(); + } + + @Override + void onCancel() { + drain(); + } + + //impl note: don't use isTerminated() in the drain loop, + //it needs to first check the `done` status before setting `disposable` to TERMINATED + //otherwise it would either loose the ability to drain or the ability to invoke the + //handler at the right time. + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + final Subscriber a = actual; + final Queue q = queue; + + for (; ; ) { + long r = requested; + long e = 0L; + + while (e != r) { + if (isCancelled()) { + Operators.onDiscardQueueWithClear(q, ctx, null); + if (WIP.decrementAndGet(this) != 0) { + continue; + } + else { + return; + } + } + + boolean d = done; + + T o = q.poll(); + + boolean empty = o == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + super.error(ex); + } + else { + super.complete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(o); + + e++; + } + + if (e == r) { + if (isCancelled()) { + Operators.onDiscardQueueWithClear(q, ctx, null); + if (WIP.decrementAndGet(this) != 0) { + continue; + } + else { + return; + } + } + + boolean d = done; + + boolean empty = q.isEmpty(); + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + super.error(ex); + } + else { + super.complete(); + } + return; + } + } + + if (e != 0) { + Operators.produced(REQUESTED, this, e); + } + + if (WIP.decrementAndGet(this) == 0) { + break; + } + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.BUFFERED) { + return queue.size(); + } + if (key == Attr.TERMINATED) { + return done; + } + if (key == Attr.ERROR) { + return error; + } + + return super.scanUnsafe(key); + } + + @Override + public String toString() { + return "FluxSink(" + OverflowStrategy.BUFFER + ")"; + } + } + + static final class LatestAsyncSink extends BaseSink { + + final AtomicReference queue; + + Throwable error; + volatile boolean done; //done is still useful to be able to drain before the terminated handler is executed + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(LatestAsyncSink.class, "wip"); + + LatestAsyncSink(CoreSubscriber actual) { + super(actual); + this.queue = new AtomicReference<>(); + } + + @Override + public FluxSink next(T t) { + T old = queue.getAndSet(t); + Operators.onDiscard(old, ctx); + drain(); + return this; + } + + @Override + public void error(Throwable e) { + error = e; + done = true; + drain(); + } + + @Override + public void complete() { + done = true; + drain(); + } + + @Override + void onRequestedFromDownstream() { + drain(); + } + + @Override + void onCancel() { + drain(); + } + + //impl note: don't use isTerminated() in the drain loop, + //it needs to first check the `done` status before setting `disposable` to TERMINATED + //otherwise it would either loose the ability to drain or the ability to invoke the + //handler at the right time. + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + final Subscriber a = actual; + final AtomicReference q = queue; + + for (; ; ) { + long r = requested; + long e = 0L; + + while (e != r) { + if (isCancelled()) { + T old = q.getAndSet(null); + Operators.onDiscard(old, ctx); + if (WIP.decrementAndGet(this) != 0) { + continue; + } + else { + return; + } + } + + boolean d = done; + + T o = q.getAndSet(null); + + boolean empty = o == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + super.error(ex); + } + else { + super.complete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(o); + + e++; + } + + if (e == r) { + if (isCancelled()) { + T old = q.getAndSet(null); + Operators.onDiscard(old, ctx); + if (WIP.decrementAndGet(this) != 0) { + continue; + } + else { + return; + } + } + + boolean d = done; + + boolean empty = q.get() == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + super.error(ex); + } + else { + super.complete(); + } + return; + } + } + + if (e != 0) { + Operators.produced(REQUESTED, this, e); + } + + if (WIP.decrementAndGet(this) == 0) { + break; + } + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.BUFFERED) { + return queue.get() == null ? 0 : 1; + } + if (key == Attr.TERMINATED) { + return done; + } + if (key == Attr.ERROR) { + return error; + } + + return super.scanUnsafe(key); + } + + @Override + public String toString() { + return "FluxSink(" + OverflowStrategy.LATEST + ")"; + } + } + + static final class SinkDisposable implements Disposable { + + Disposable onCancel; + + Disposable disposable; + + SinkDisposable(@Nullable Disposable disposable, @Nullable Disposable onCancel) { + this.disposable = disposable; + this.onCancel = onCancel; + } + + @Override + public void dispose() { + if (disposable != null) { + disposable.dispose(); + } + } + + public void cancel() { + if (onCancel != null) { + onCancel.dispose(); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDefaultIfEmpty.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDefaultIfEmpty.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDefaultIfEmpty.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Emits a scalar value if the source sequence turns out to be empty. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxDefaultIfEmpty extends InternalFluxOperator { + + final T value; + + FluxDefaultIfEmpty(Flux source, T value) { + super(source); + this.value = Objects.requireNonNull(value, "value"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new DefaultIfEmptySubscriber<>(actual, value); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class DefaultIfEmptySubscriber + extends Operators.MonoSubscriber { + + Subscription s; + + boolean hasValue; + + DefaultIfEmptySubscriber(CoreSubscriber actual, T value) { + super(actual); + //noinspection deprecation + this.value = value; //we write once, setValue() is NO-OP + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + 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); + 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); + } + } + + @Override + public void onNext(T t) { + if (!hasValue) { + hasValue = true; + } + + actual.onNext(t); + } + + @Override + public void onComplete() { + if (hasValue) { + actual.onComplete(); + } else { + complete(this.value); + } + } + + @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. + } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; // prevent fusion because of the upstream + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDefer.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDefer.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDefer.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016-2021 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.Supplier; + +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; + +/** + * Defers the creation of the actual Publisher the Subscriber will be subscribed to. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxDefer extends Flux implements SourceProducer { + + final Supplier> supplier; + + FluxDefer(Supplier> supplier) { + this.supplier = Objects.requireNonNull(supplier, "supplier"); + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + Publisher p; + + try { + p = Objects.requireNonNull(supplier.get(), + "The Publisher returned by the supplier is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + + from(p).subscribe(actual); + } + + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDeferContextual.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDeferContextual.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDeferContextual.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; + +import reactor.core.CoreSubscriber; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +/** + * Defers the creation of the actual Publisher the Subscriber will be subscribed to. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxDeferContextual extends Flux implements SourceProducer { + + final Function> contextualPublisherFactory; + + FluxDeferContextual(Function> contextualPublisherFactory) { + this.contextualPublisherFactory = Objects.requireNonNull(contextualPublisherFactory, "contextualPublisherFactory"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Publisher p; + + Context ctx = actual.currentContext(); + try { + p = Objects.requireNonNull(contextualPublisherFactory.apply(ctx.readOnly()), + "The Publisher returned by the contextualPublisherFactory is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, ctx)); + return; + } + + from(p).subscribe(actual); + } + + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDelaySequence.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDelaySequence.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDelaySequence.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2017-2021 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.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.scheduler.Scheduler; + +/** + * @author Simon Baslé + */ +//adapted from RxJava2 FlowableDelay: https://github.com/ReactiveX/RxJava/blob/2.x/src/main/java/io/reactivex/internal/operators/flowable/FlowableDelay.java +final class FluxDelaySequence extends InternalFluxOperator { + + final Duration delay; + final Scheduler scheduler; + + FluxDelaySequence(Flux source, Duration delay, Scheduler scheduler) { + super(source); + this.delay = delay; + this.scheduler = scheduler; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + Scheduler.Worker w = scheduler.createWorker(); + + return new DelaySubscriber(actual, delay, w); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return super.scanUnsafe(key); + } + + static final class DelaySubscriber implements InnerOperator { + + final CoreSubscriber actual; + final long delay; + final TimeUnit timeUnit; + final Scheduler.Worker w; + + Subscription s; + + volatile boolean done; + + volatile long delayed; + static final AtomicLongFieldUpdater DELAYED = + AtomicLongFieldUpdater.newUpdater(DelaySubscriber.class, "delayed"); + + + DelaySubscriber(CoreSubscriber actual, Duration delay, Scheduler.Worker w) { + super(); + this.actual = Operators.serialize(actual); + this.w = w; + this.delay = delay.toNanos(); + this.timeUnit = TimeUnit.NANOSECONDS; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(final T t) { + if (done || delayed < 0) { + Operators.onNextDropped(t, currentContext()); + return; + } + //keep track of the number of delayed onNext so that + //we can also delay onError/onComplete when an onNext + //is "in flight" + DELAYED.incrementAndGet(this); + w.schedule(() -> delayedNext(t), delay, timeUnit); + } + + private void delayedNext(T t) { + //this onNext has been delayed and now processed + DELAYED.decrementAndGet(this); + actual.onNext(t); + } + + @Override + public void onError(final Throwable t) { + if (done) { + Operators.onErrorDropped(t, currentContext()); + return; + } + done = true; + //if no currently delayed onNext (eg. empty source), + // we can immediately error + if (DELAYED.compareAndSet(this, 0, -1)) { + actual.onError(t); + } + else { + w.schedule(new OnError(t), delay, timeUnit); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + //if no currently delayed onNext (eg. empty source), + // we can immediately complete + if (DELAYED.compareAndSet(this, 0, -1)) { + actual.onComplete(); + } + else { + w.schedule(new OnComplete(), delay, timeUnit); + } + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + w.dispose(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.RUN_ON) return w; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return w.isDisposed() && !done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + final class OnError implements Runnable { + private final Throwable t; + + OnError(Throwable t) { + this.t = t; + } + + @Override + public void run() { + try { + actual.onError(t); + } finally { + w.dispose(); + } + } + } + + final class OnComplete implements Runnable { + @Override + public void run() { + try { + actual.onComplete(); + } finally { + w.dispose(); + } + } + } + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDelaySubscription.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDelaySubscription.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDelaySubscription.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Delays the subscription to the main source until another Publisher + * signals a value or completes. + * + * @param the main source value type + * @param the other source type + * @see Reactive-Streams-Commons + */ +final class FluxDelaySubscription extends InternalFluxOperator + implements Consumer> { + + final Publisher other; + + FluxDelaySubscription(Flux source, Publisher other) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + other.subscribe(new DelaySubscriptionOtherSubscriber<>(actual, this)); + return null; + } + + @Override + public void accept(DelaySubscriptionOtherSubscriber s) { + source.subscribe(new DelaySubscriptionMainSubscriber<>(s.actual, s)); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class DelaySubscriptionOtherSubscriber + extends Operators.DeferredSubscription implements InnerOperator { + + final Consumer> source; + + final CoreSubscriber actual; + + Subscription s; + + boolean done; + + DelaySubscriptionOtherSubscriber(CoreSubscriber actual, + Consumer> source) { + this.actual = actual; + this.source = source; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return actual; + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @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(U t) { + if (done) { + return; + } + done = true; + s.cancel(); + + source.accept(this); + } + + @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; + + source.accept(this); + } + } + + static final class DelaySubscriptionMainSubscriber + implements InnerConsumer { + + final CoreSubscriber actual; + + final DelaySubscriptionOtherSubscriber arbiter; + + DelaySubscriptionMainSubscriber(CoreSubscriber actual, + DelaySubscriptionOtherSubscriber arbiter) { + this.actual = actual; + this.arbiter = arbiter; + } + + @Override + public Context currentContext() { + return arbiter.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) return actual; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + arbiter.set(s); + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDematerialize.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDematerialize.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDematerialize.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2016-2021 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.util.annotation.Nullable; + +/** + * @author Stephane Maldini + */ +final class FluxDematerialize extends InternalFluxOperator, T> { + + FluxDematerialize(Flux> source) { + super(source); + } + + @Override + public CoreSubscriber> subscribeOrReturn(CoreSubscriber actual) { + return new DematerializeSubscriber<>(actual, false); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class DematerializeSubscriber implements InnerOperator, T> { + + final CoreSubscriber actual; + final boolean completeAfterOnNext; + + Subscription s; + + boolean done; + + volatile boolean cancelled; + + DematerializeSubscriber(CoreSubscriber subscriber, boolean completeAfterOnNext) { + this.actual = subscriber; + this.completeAfterOnNext = completeAfterOnNext; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.PREFETCH) return 0; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(Signal t) { + if (done) { + //TODO interpret the Signal and drop differently? + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + if (t.isOnComplete()) { + s.cancel(); + onComplete(); + } + else if (t.isOnError()) { + s.cancel(); + onError(t.getThrowable()); + } + else if (t.isOnNext()) { + actual.onNext(t.get()); + if (completeAfterOnNext) { + onComplete(); + } + } + } + + @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; + actual.onComplete(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + s.request(n); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + s.cancel(); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDetach.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDetach.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDetach.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016-2021 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.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Detaches both the child Subscriber and the Subscription on + * termination or cancellation. + *

This should help with odd retention scenarios when running + * with non Rx mentality based Publishers. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxDetach extends InternalFluxOperator { + + FluxDetach(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new DetachSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class DetachSubscriber implements InnerOperator { + + CoreSubscriber actual; + + Subscription s; + + DetachSubscriber(CoreSubscriber actual) { + this.actual = actual; + } + + @Override + public Context currentContext() { + return actual == null ? Context.empty() : actual.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return actual == null; + if (key == Attr.CANCELLED) return actual == null && s == null; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + Subscriber a = actual; + if (a != null) { + a.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + Subscriber a = actual; + if (a != null) { + actual = null; + s = null; + + a.onError(t); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onComplete() { + Subscriber a = actual; + if (a != null) { + actual = null; + s = null; + + a.onComplete(); + } + } + + @Override + public void request(long n) { + Subscription a = s; + if (a != null) { + a.request(n); + } + } + + @Override + public void cancel() { + Subscription a = s; + if (a != null) { + actual = null; + s = null; + + a.cancel(); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDistinct.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDistinct.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDistinct.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,609 @@ +/* + * Copyright (c) 2016-2021 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.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.core.Fuseable.QueueSubscription; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * For each subscriber, tracks the source values that have been seen and + * filters out duplicates. + * + * @param the source value type + * @param the key extracted from the source value to be used for duplicate testing + * @param the backing store type used together with the keys when testing for duplicates with {@link BiPredicate} + * + * @see Reactive-Streams-Commons + */ +final class FluxDistinct extends InternalFluxOperator { + + final Function keyExtractor; + final Supplier collectionSupplier; + final BiPredicate distinctPredicate; + final Consumer cleanupCallback; + + FluxDistinct(Flux source, + Function keyExtractor, + Supplier collectionSupplier, + BiPredicate distinctPredicate, + Consumer cleanupCallback) { + super(source); + this.keyExtractor = Objects.requireNonNull(keyExtractor, "keyExtractor"); + this.collectionSupplier = Objects.requireNonNull(collectionSupplier, "collectionSupplier"); + this.distinctPredicate = Objects.requireNonNull(distinctPredicate, "distinctPredicate"); + this.cleanupCallback = Objects.requireNonNull(cleanupCallback, "cleanupCallback"); + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + C collection = Objects.requireNonNull(collectionSupplier.get(), + "The collectionSupplier returned a null collection"); + + if (actual instanceof ConditionalSubscriber) { + return new DistinctConditionalSubscriber<>((ConditionalSubscriber) actual, + collection, + keyExtractor, + distinctPredicate, cleanupCallback); + } + else { + return new DistinctSubscriber<>(actual, collection, keyExtractor, distinctPredicate, + cleanupCallback); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class DistinctSubscriber + implements ConditionalSubscriber, InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + + final C collection; + + final Function keyExtractor; + + final BiPredicate distinctPredicate; + + final Consumer cleanupCallback; + + Subscription s; + + boolean done; + + DistinctSubscriber(CoreSubscriber actual, + C collection, + Function keyExtractor, + BiPredicate distinctPredicate, + Consumer cleanupCallback) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.collection = collection; + this.keyExtractor = keyExtractor; + this.distinctPredicate = distinctPredicate; + this.cleanupCallback = cleanupCallback; + } + + @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 (!tryOnNext(t)) { + s.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, this.ctx); + return true; + } + + K k; + + try { + k = Objects.requireNonNull(keyExtractor.apply(t), + "The distinct extractor returned a null value."); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, this.ctx)); + Operators.onDiscard(t, this.ctx); + return true; + } + + boolean b; + + try { + b = distinctPredicate.test(collection, k); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + Operators.onDiscard(t, this.ctx); + return true; + } + + if (b) { + actual.onNext(t); + return true; + } + Operators.onDiscard(t , ctx); + return false; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, this.ctx); + return; + } + done = true; + cleanupCallback.accept(collection); + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + cleanupCallback.accept(collection); + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + if (collection != null) { + cleanupCallback.accept(collection); + } + } + + @Override + 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); + } + } + + static final class DistinctConditionalSubscriber + implements ConditionalSubscriber, InnerOperator { + + final ConditionalSubscriber actual; + final Context ctx; + + final C collection; + + final Function keyExtractor; + + final BiPredicate distinctPredicate; + final Consumer cleanupCallback; + + Subscription s; + + boolean done; + + DistinctConditionalSubscriber(ConditionalSubscriber actual, + C collection, + Function keyExtractor, + BiPredicate distinctPredicate, + Consumer cleanupCallback) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.collection = collection; + this.keyExtractor = keyExtractor; + this.distinctPredicate = distinctPredicate; + this.cleanupCallback = cleanupCallback; + } + + @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, this.ctx); + return; + } + + K k; + + try { + k = Objects.requireNonNull(keyExtractor.apply(t), + "The distinct extractor returned a null value."); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, this.ctx)); + Operators.onDiscard(t, this.ctx); + return; + } + + boolean b; + + try { + b = distinctPredicate.test(collection, k); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, this.ctx)); + Operators.onDiscard(t, this.ctx); + return; + } + + if (b) { + actual.onNext(t); + } + else { + Operators.onDiscard(t, ctx); + s.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, this.ctx); + return true; + } + + K k; + + try { + k = Objects.requireNonNull(keyExtractor.apply(t), + "The distinct extractor returned a null value."); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, this.ctx)); + Operators.onDiscard(t, this.ctx); + return true; + } + + boolean b; + + try { + b = distinctPredicate.test(collection, k); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, this.ctx)); + Operators.onDiscard(t, this.ctx); + return true; + } + + if (b) { + return actual.tryOnNext(t); + } + else { + Operators.onDiscard(t, ctx); + return false; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, this.ctx); + return; + } + done = true; + cleanupCallback.accept(collection); + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + cleanupCallback.accept(collection); + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + if (collection != null) { + cleanupCallback.accept(collection); + } + } + + @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); + } + } + + static final class DistinctFuseableSubscriber + implements ConditionalSubscriber, InnerOperator, + QueueSubscription { + + final CoreSubscriber actual; + final Context ctx; + + final C collection; + + final Function keyExtractor; + final BiPredicate distinctPredicate; + final Consumer cleanupCallback; + + QueueSubscription qs; + + boolean done; + + int sourceMode; + + DistinctFuseableSubscriber(CoreSubscriber actual, C collection, + Function keyExtractor, + BiPredicate predicate, + Consumer callback) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.collection = collection; + this.keyExtractor = keyExtractor; + this.distinctPredicate = predicate; + this.cleanupCallback = callback; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.qs, s)) { + this.qs = (QueueSubscription) s; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (!tryOnNext(t)) { + qs.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (sourceMode == Fuseable.ASYNC) { + actual.onNext(null); + return true; + } + if (done) { + Operators.onNextDropped(t, this.ctx); + return true; + } + + K k; + + try { + k = Objects.requireNonNull(keyExtractor.apply(t), + "The distinct extractor returned a null value."); + } + catch (Throwable e) { + onError(Operators.onOperatorError(qs, e, t, this.ctx)); + Operators.onDiscard(t, this.ctx); + return true; + } + + boolean b; + + try { + b = distinctPredicate.test(collection, k); + } + catch (Throwable e) { + onError(Operators.onOperatorError(qs, e, t, this.ctx)); + Operators.onDiscard(t, this.ctx); + return true; + } + + if (b) { + actual.onNext(t); + return true; + } + Operators.onDiscard(t, ctx); + return false; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, this.ctx); + return; + } + done = true; + cleanupCallback.accept(collection); + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + cleanupCallback.accept(collection); + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + qs.request(n); + } + + @Override + public void cancel() { + qs.cancel(); + if (collection != null) { + cleanupCallback.accept(collection); + } + } + + @Override + public int requestFusion(int requestedMode) { + int m = qs.requestFusion(requestedMode); + sourceMode = m; + return m; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return qs; + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + @Nullable + public T poll() { + if (sourceMode == Fuseable.ASYNC) { + long dropped = 0; + for (; ; ) { + T v = qs.poll(); + if (v == null) { + return null; + } + try { + K r = Objects.requireNonNull(keyExtractor.apply(v), + "The keyExtractor returned a null collection"); + + if (distinctPredicate.test(collection, r)) { + if (dropped != 0) { + request(dropped); + } + return v; + } + Operators.onDiscard(v, ctx); + dropped++; + } + catch (Throwable error) { + Operators.onDiscard(v, this.ctx); + throw error; + } + } + } + else { + for (; ; ) { + T v = qs.poll(); + if (v == null) { + return null; + } + try { + K r = Objects.requireNonNull(keyExtractor.apply(v), + "The keyExtractor returned a null collection"); + + if (distinctPredicate.test(collection, r)) { + return v; + } + else { + Operators.onDiscard(v, ctx); + } + } + catch (Throwable error) { + Operators.onDiscard(v, this.ctx); + throw error; + } + } + } + } + + @Override + public boolean isEmpty() { + return qs.isEmpty(); + } + + @Override + public void clear() { + qs.clear(); + cleanupCallback.accept(collection); + } + + @Override + public int size() { + return qs.size(); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDistinctFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDistinctFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDistinctFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016-2021 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.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.publisher.FluxDistinct.DistinctFuseableSubscriber; + +/** + * For each subscriber, tracks the source values that have been seen and + * filters out duplicates. + * + * @param the source value type + * @param the key extracted from the source value to be used for duplicate testing + * @param the backing store type used together with the keys when testing for duplicates with {@link BiPredicate} + * + * @see Reactive-Streams-Commons + */ +final class FluxDistinctFuseable + extends InternalFluxOperator implements Fuseable { + + final Function keyExtractor; + final Supplier collectionSupplier; + final BiPredicate distinctPredicate; + final Consumer cleanupCallback; + + FluxDistinctFuseable(Flux source, + Function keyExtractor, Supplier collectionSupplier, + BiPredicate distinctPredicate, Consumer cleanupCallback) { + super(source); + this.keyExtractor = Objects.requireNonNull(keyExtractor, "keyExtractor"); + this.collectionSupplier = Objects.requireNonNull(collectionSupplier, "collectionSupplier"); + this.distinctPredicate = Objects.requireNonNull(distinctPredicate, "distinctPredicate"); + this.cleanupCallback = Objects.requireNonNull(cleanupCallback, "cleanupCallback"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + C collection = Objects.requireNonNull(collectionSupplier.get(), + "The collectionSupplier returned a null collection"); + + return new DistinctFuseableSubscriber<>(actual, collection, keyExtractor, + distinctPredicate, cleanupCallback); + } + + @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/FluxDistinctUntilChanged.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDistinctUntilChanged.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDistinctUntilChanged.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2016-2021 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.BiPredicate; +import java.util.function.Function; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Filters out subsequent and repeated elements. + * + * @param the value type + * @param the key type used for comparing subsequent elements + * @see Reactive-Streams-Commons + */ +final class FluxDistinctUntilChanged extends InternalFluxOperator { + + final Function keyExtractor; + final BiPredicate keyComparator; + + FluxDistinctUntilChanged(Flux source, + Function keyExtractor, + BiPredicate keyComparator) { + super(source); + this.keyExtractor = Objects.requireNonNull(keyExtractor, "keyExtractor"); + this.keyComparator = Objects.requireNonNull(keyComparator, "keyComparator"); + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new DistinctUntilChangedConditionalSubscriber<>((ConditionalSubscriber) actual, + keyExtractor, keyComparator); + } + else { + return new DistinctUntilChangedSubscriber<>(actual, keyExtractor, keyComparator); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class DistinctUntilChangedSubscriber + implements ConditionalSubscriber, InnerOperator { + final CoreSubscriber actual; + final Context ctx; + + final Function keyExtractor; + final BiPredicate keyComparator; + + Subscription s; + + boolean done; + + @Nullable + K lastKey; + + DistinctUntilChangedSubscriber(CoreSubscriber actual, + Function keyExtractor, + BiPredicate keyComparator) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.keyExtractor = keyExtractor; + this.keyComparator = keyComparator; + } + + @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 (!tryOnNext(t)) { + s.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, ctx); + return true; + } + + K k; + + try { + k = Objects.requireNonNull(keyExtractor.apply(t), + "The distinct extractor returned a null value."); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + Operators.onDiscard(t, ctx); + return true; + } + + if (null == lastKey) { + lastKey = k; + actual.onNext(t); + return true; + } + + boolean equiv; + + try { + equiv = keyComparator.test(lastKey, k); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + Operators.onDiscard(t, ctx); + return true; + } + + if (equiv) { + Operators.onDiscard(t, ctx); + return false; + } + + lastKey = k; + actual.onNext(t); + return true; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, ctx); + return; + } + done = true; + lastKey = null; + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + lastKey = null; + + actual.onComplete(); + } + + @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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + lastKey = null; + } + } + + static final class DistinctUntilChangedConditionalSubscriber + implements ConditionalSubscriber, InnerOperator { + final ConditionalSubscriber actual; + final Context ctx; + + final Function keyExtractor; + final BiPredicate keyComparator; + + Subscription s; + + boolean done; + + @Nullable + K lastKey; + + DistinctUntilChangedConditionalSubscriber(ConditionalSubscriber actual, + Function keyExtractor, + BiPredicate keyComparator) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.keyExtractor = keyExtractor; + this.keyComparator = keyComparator; + } + + @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 (!tryOnNext(t)) { + s.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, ctx); + return true; + } + + K k; + + try { + k = Objects.requireNonNull(keyExtractor.apply(t), + "The distinct extractor returned a null value."); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + Operators.onDiscard(t, ctx); + return true; + } + + if (null == lastKey) { + lastKey = k; + return actual.tryOnNext(t); + } + + boolean equiv; + + try { + equiv = keyComparator.test(lastKey, k); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + Operators.onDiscard(t, ctx); + return true; + } + + if (equiv) { + Operators.onDiscard(t, ctx); + return false; + } + + lastKey = k; + return actual.tryOnNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, ctx); + return; + } + done = true; + lastKey = null; + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + lastKey = null; + + actual.onComplete(); + } + + @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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + lastKey = null; + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinally.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinally.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinally.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.function.Consumer; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.core.Fuseable.QueueSubscription; +import reactor.util.annotation.Nullable; + +/** + * Hook into the lifecycle events and signals of a {@link Flux} and execute + * a provided callback after any of onComplete, onError and cancel events. + * The hook is executed only once and receives the event type that triggered + * it ({@link SignalType#ON_COMPLETE}, {@link SignalType#ON_ERROR} or + * {@link SignalType#CANCEL}). + *

+ * Note that any exception thrown by the hook are caught and bubbled up + * using {@link Operators#onErrorDropped(Throwable, reactor.util.context.Context)}. + * + * @param the value type + * @author Simon Baslé + */ +final class FluxDoFinally extends InternalFluxOperator { + + final Consumer onFinally; + + @SuppressWarnings("unchecked") + static CoreSubscriber createSubscriber( + CoreSubscriber s, Consumer onFinally, + boolean fuseable) { + + 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); + } + return new DoFinallySubscriber<>(s, onFinally); + } + + FluxDoFinally(Flux source, Consumer onFinally) { + super(source); + this.onFinally = onFinally; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return createSubscriber(actual, onFinally, false); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static class DoFinallySubscriber implements InnerOperator { + + final CoreSubscriber actual; + + final Consumer onFinally; + + volatile int once; + + 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; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED || key == Attr.CANCELLED) + return once == 1; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + 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); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + try { + actual.onError(t); + } + finally { + runFinally(SignalType.ON_ERROR); + } + } + + @Override + public void onComplete() { + actual.onComplete(); + runFinally(SignalType.ON_COMPLETE); + } + + @Override + public void cancel() { + s.cancel(); + runFinally(SignalType.CANCEL); + } + + @Override + public void request(long n) { + s.request(n); + } + + void runFinally(SignalType signalType) { + if (ONCE.compareAndSet(this, 0, 1)) { + try { + onFinally.accept(signalType); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + Operators.onErrorDropped(ex, actual.currentContext()); + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + } + + 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 { + + DoFinallyConditionalSubscriber(ConditionalSubscriber actual, + Consumer onFinally) { + super(actual, onFinally); + } + + @Override + @SuppressWarnings("unchecked") + public boolean tryOnNext(T t) { + 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); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinallyFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinallyFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFinallyFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Hook with fusion into the lifecycle events and signals of a {@link Flux} + * and execute a provided callback after any of onComplete, onError and cancel events. + * The hook is executed only once and receives the event type that triggered + * it ({@link SignalType#ON_COMPLETE}, {@link SignalType#ON_ERROR} or + * {@link SignalType#CANCEL}). + *

+ * Note that any exception thrown by the hook are caught and bubbled up + * using {@link Operators#onErrorDropped(Throwable, reactor.util.context.Context)}. + * + * @param the value type + * @author Simon Baslé + */ +final class FluxDoFinallyFuseable extends InternalFluxOperator implements Fuseable { + + final Consumer onFinally; + + FluxDoFinallyFuseable(Flux source, Consumer onFinally) { + super(source); + this.onFinally = onFinally; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return FluxDoFinally.createSubscriber(actual, onFinally, true); + } + + @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/FluxDoFirst.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFirst.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFirst.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-2021 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.Subscriber; + +import reactor.core.CoreSubscriber; + +/** + * Hook into the {@link org.reactivestreams.Publisher#subscribe(Subscriber)} of a + * {@link Flux} and execute a provided callback before calling + * {@link org.reactivestreams.Publisher#subscribe(Subscriber)} directly with the + * {@link CoreSubscriber}. + * + *

+ * Note that any exceptions thrown by the hook short circuit the subscription process and + * are forwarded to the {@link Subscriber}'s {@link Subscriber#onError(Throwable)} method. + * + * @param the value type + * @author Simon Baslé + */ +final class FluxDoFirst extends InternalFluxOperator { + + final Runnable onFirst; + + FluxDoFirst(Flux source, Runnable onFirst) { + super(source); + this.onFirst = onFirst; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + onFirst.run(); + return actual; + } + + @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/FluxDoFirstFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFirstFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDoFirstFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016-2021 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.Subscriber; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Hook into the {@link org.reactivestreams.Publisher#subscribe(Subscriber)} of a + * {@link Fuseable} {@link Flux} and execute a provided callback before calling + * {@link org.reactivestreams.Publisher#subscribe(Subscriber)} directly with the + * {@link CoreSubscriber}. + * + *

+ * Note that any exceptions thrown by the hook short circuit the subscription process and + * are forwarded to the {@link Subscriber}'s {@link Subscriber#onError(Throwable)} method. + * + * @param the value type + * @author Simon Baslé + */ +final class FluxDoFirstFuseable extends InternalFluxOperator implements Fuseable { + + final Runnable onFirst; + + FluxDoFirstFuseable(Flux fuseableSource, Runnable onFirst) { + super(fuseableSource); + this.onFirst = onFirst; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + onFirst.run(); + + return actual; + } + + @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/FluxDoOnEach.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDoOnEach.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDoOnEach.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +import static reactor.core.Scannable.Attr.RUN_STYLE; +import static reactor.core.Scannable.Attr.RunStyle.SYNC; + +/** + * Peek into the lifecycle events and signals of a sequence + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxDoOnEach extends InternalFluxOperator { + + final Consumer> onSignal; + + FluxDoOnEach(Flux source, Consumer> onSignal) { + super(source); + this.onSignal = Objects.requireNonNull(onSignal, "onSignal"); + } + + @SuppressWarnings("unchecked") + static DoOnEachSubscriber createSubscriber(CoreSubscriber actual, + Consumer> onSignal, boolean fuseable, boolean isMono) { + if (fuseable) { + if(actual instanceof ConditionalSubscriber) { + return new DoOnEachFuseableConditionalSubscriber<>((ConditionalSubscriber) actual, onSignal, isMono); + } + return new DoOnEachFuseableSubscriber<>(actual, onSignal, isMono); + } + + if (actual instanceof ConditionalSubscriber) { + return new DoOnEachConditionalSubscriber<>((ConditionalSubscriber) actual, onSignal, isMono); + } + return new DoOnEachSubscriber<>(actual, onSignal, isMono); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return createSubscriber(actual, onSignal, false, false); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == RUN_STYLE) return SYNC; + return super.scanUnsafe(key); + } + + static class DoOnEachSubscriber implements InnerOperator, Signal { + + static final short STATE_FLUX_START = (short) 0; + static final short STATE_MONO_START = (short) 1; + static final short STATE_SKIP_HANDLER = (short) 2; + static final short STATE_DONE = (short) 3; + + final CoreSubscriber actual; + final Context cachedContext; + final Consumer> onSignal; + + T t; + + Subscription s; + @Nullable + Fuseable.QueueSubscription qs; + + short state; + + DoOnEachSubscriber(CoreSubscriber actual, + Consumer> onSignal, + boolean monoFlavor) { + this.actual = actual; + this.cachedContext = actual.currentContext(); + this.onSignal = onSignal; + this.state = monoFlavor ? STATE_MONO_START : STATE_FLUX_START; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + this.qs = Operators.as(s); + actual.onSubscribe(this); + } + } + + @Override + public Context currentContext() { + return cachedContext; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.TERMINATED) { + return state == STATE_DONE; + } + if (key == RUN_STYLE) { + return SYNC; + } + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onNext(T t) { + if (state == STATE_DONE) { + Operators.onNextDropped(t, cachedContext); + return; + } + try { + this.t = t; + onSignal.accept(this); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, cachedContext)); + return; + } + + if (state == STATE_MONO_START) { + state = STATE_SKIP_HANDLER; + try { + onSignal.accept(Signal.complete(cachedContext)); + } + catch (Throwable e) { + state = STATE_MONO_START; + onError(Operators.onOperatorError(s, e, cachedContext)); + return; + } + } + + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (state == STATE_DONE) { + Operators.onErrorDropped(t, cachedContext); + return; + } + boolean applyHandler = state < STATE_SKIP_HANDLER; + state = STATE_DONE; + if (applyHandler) { + try { + onSignal.accept(Signal.error(t, cachedContext)); + } + catch (Throwable e) { + //this performs a throwIfFatal or suppresses t in e + t = Operators.onOperatorError(null, e, t, cachedContext); + } + } + + try { + actual.onError(t); + } + catch (UnsupportedOperationException use) { + if (!Exceptions.isErrorCallbackNotImplemented(use) && use.getCause() != t) { + throw use; + } + //ignore if missing callback + } + } + + @Override + public void onComplete() { + if (state == STATE_DONE) { + return; + } + short oldState = state; + state = STATE_DONE; + if (oldState < STATE_SKIP_HANDLER) { + try { + onSignal.accept(Signal.complete(cachedContext)); + } + catch (Throwable e) { + state = oldState; + onError(Operators.onOperatorError(s, e, cachedContext)); + return; + } + } + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Nullable + @Override + public Throwable getThrowable() { + return null; + } + + @Nullable + @Override + public Subscription getSubscription() { + return null; + } + + @Nullable + @Override + public T get() { + return t; + } + + @Override + public ContextView getContextView() { + return cachedContext; + } + + @Override + public SignalType getType() { + return SignalType.ON_NEXT; + } + + @Override + public String toString() { + return "doOnEach_onNext(" + t + ")"; + } + } + + static class DoOnEachFuseableSubscriber extends DoOnEachSubscriber + implements Fuseable, Fuseable.QueueSubscription { + + boolean syncFused; + + DoOnEachFuseableSubscriber(CoreSubscriber actual, + Consumer> onSignal, boolean isMono) { + super(actual, onSignal, isMono); + } + + @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() { + qs.clear(); //throws NPE, but should only be called after onSubscribe on a Fuseable + } + + @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) { + state = STATE_DONE; + try { + onSignal.accept(Signal.complete(cachedContext)); + } + catch (Throwable e) { + throw e; + } + } else if (v != null) { + this.t = v; + onSignal.accept(this); //throws in case of error + } + return v; + } + + @Override + public int size() { + return qs == null ? 0 : qs.size(); + } + } + + static final class DoOnEachConditionalSubscriber extends DoOnEachSubscriber + implements ConditionalSubscriber { + + DoOnEachConditionalSubscriber(ConditionalSubscriber actual, + Consumer> onSignal, boolean isMono) { + super(actual, onSignal, isMono); + } + + @Override + @SuppressWarnings("unchecked") + public boolean tryOnNext(T t) { + boolean result = ((ConditionalSubscriber)actual).tryOnNext(t); + if (result) { + this.t = t; + onSignal.accept(this); + //TODO also apply the handler with onComplete if there is a way to trigger tryOnNext from a Mono? + //TODO if so, should `tryOnNext` be called first? + } + return result; + } + } + + static final class DoOnEachFuseableConditionalSubscriber extends DoOnEachFuseableSubscriber + implements ConditionalSubscriber { + + DoOnEachFuseableConditionalSubscriber(ConditionalSubscriber actual, + Consumer> onSignal, boolean isMono) { + super(actual, onSignal, isMono); + } + + @Override + @SuppressWarnings("unchecked") + public boolean tryOnNext(T t) { + boolean result = ((ConditionalSubscriber) actual).tryOnNext(t); + if (result) { + this.t = t; + onSignal.accept(this); + //TODO also apply the handler with onComplete if there is a way to trigger tryOnNext from a Mono? + } + return result; + } + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxDoOnEachFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxDoOnEachFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxDoOnEachFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Peek into the lifecycle events and signals of a sequence, {@link Fuseable} version of + * {@link FluxDoOnEach}. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxDoOnEachFuseable extends InternalFluxOperator implements Fuseable { + + final Consumer> onSignal; + + FluxDoOnEachFuseable(Flux source, Consumer> onSignal) { + super(source); + this.onSignal = Objects.requireNonNull(onSignal, "onSignal"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return FluxDoOnEach.createSubscriber(actual, this.onSignal, true, false); + } + + @Override + public Object scanUnsafe(Attr key) { + 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/FluxElapsed.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxElapsed.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxElapsed.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2016-2021 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.TimeUnit; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +/** + * @author Stephane Maldini + */ +final class FluxElapsed extends InternalFluxOperator> implements Fuseable { + + final Scheduler scheduler; + + FluxElapsed(Flux source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new ElapsedSubscriber<>(actual, scheduler); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + static final class ElapsedSubscriber + implements InnerOperator>, + QueueSubscription> { + + final CoreSubscriber> actual; + final Scheduler scheduler; + + Subscription s; + QueueSubscription qs; + + long lastTime; + + ElapsedSubscriber(CoreSubscriber> actual, + Scheduler scheduler) { + this.actual = actual; + this.scheduler = scheduler; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + lastTime = scheduler.now(TimeUnit.MILLISECONDS); + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + public CoreSubscriber> actual() { + return actual; + } + + @Override + public void onNext(T t) { + if(t == null){ + actual.onNext(null); + return; + } + actual.onNext(snapshot(t)); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public int requestFusion(int requestedMode) { + QueueSubscription qs = Operators.as(s); + if (qs != null) { + this.qs = qs; + return qs.requestFusion(requestedMode); + } + return Fuseable.NONE; + } + + Tuple2 snapshot(T data){ + long now = scheduler.now(TimeUnit.MILLISECONDS); + long last = lastTime; + lastTime = now; + long delta = now - last; + return Tuples.of(delta, data); + } + + @Override + @Nullable + public Tuple2 poll() { + T data = qs.poll(); + if(data != null){ + return snapshot(data); + } + return null; + } + + @Override + public int size() { + return qs.size(); + } + + @Override + public boolean isEmpty() { + return qs.isEmpty(); + } + + @Override + public void clear() { + qs.clear(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxEmpty.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxEmpty.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxEmpty.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016-2021 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.util.annotation.Nullable; + +/** + * Represents an empty publisher which only calls onSubscribe and onComplete. + *

+ * This Publisher is effectively stateless and only a single instance exists. + * Use the {@link #instance()} method to obtain a properly type-parametrized view of it. + * @see Reactive-Streams-Commons + */ +final class FluxEmpty extends Flux + implements Fuseable.ScalarCallable, SourceProducer { + + private static final Flux INSTANCE = new FluxEmpty(); + + private FluxEmpty() { + // deliberately no op + } + + @Override + public void subscribe(CoreSubscriber actual) { + Operators.complete(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + /** + * Returns a properly parametrized instance of this empty Publisher. + * + * @param the output type + * @return a properly parametrized instance of this empty Publisher + */ + @SuppressWarnings("unchecked") + public static Flux instance() { + return (Flux) INSTANCE; + } + + @Override + @Nullable + public Object call() throws Exception { + return null; /* Scalar optimizations on empty */ + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxError.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxError.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxError.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.Exceptions; +import reactor.core.Fuseable; + +import reactor.core.CoreSubscriber; + +/** + * Emits a constant or generated Throwable instance to Subscribers. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxError extends Flux implements Fuseable.ScalarCallable, SourceProducer { + + final Throwable error; + + FluxError(Throwable error) { + this.error = Objects.requireNonNull(error); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Operators.error(actual, error); + } + + @Override + public Object call() throws Exception { + if(error instanceof Exception){ + throw ((Exception)error); + } + throw Exceptions.propagate(error); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxErrorOnRequest.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxErrorOnRequest.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxErrorOnRequest.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; + +import org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Emits a constant or generated Throwable instance to Subscribers. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxErrorOnRequest extends Flux implements SourceProducer { + + final Throwable error; + + FluxErrorOnRequest(Throwable error) { + this.error = Objects.requireNonNull(error); + } + + @Override + public void subscribe(CoreSubscriber actual) { + actual.onSubscribe(new ErrorSubscription(actual, error)); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class ErrorSubscription implements InnerProducer { + + final CoreSubscriber actual; + + final Throwable error; + + volatile int once; + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(ErrorSubscription.class, "once"); + + ErrorSubscription(CoreSubscriber actual, Throwable error) { + this.actual = actual; + this.error = error; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (ONCE.compareAndSet(this, 0, 1)) { + actual.onError(error); + } + } + } + + @Override + public void cancel() { + once = 1; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ERROR) return error; + if (key == Attr.CANCELLED || key == Attr.TERMINATED) + return once == 1; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxErrorSupplied.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxErrorSupplied.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxErrorSupplied.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016-2021 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.Supplier; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; + +/** + * Emits a generated {@link Throwable} instance to Subscribers, lazily generated via a + * provided {@link java.util.function.Supplier}. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxErrorSupplied extends Flux implements Fuseable.ScalarCallable, SourceProducer { + + final Supplier errorSupplier; + + FluxErrorSupplied(Supplier errorSupplier) { + this.errorSupplier = Objects.requireNonNull(errorSupplier, "errorSupplier"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Throwable error = Objects.requireNonNull(errorSupplier.get(), "errorSupplier produced a null Throwable"); + Operators.error(actual, error); + } + + @Override + public Object call() throws Exception { + Throwable error = Objects.requireNonNull(errorSupplier.get(), "errorSupplier produced a null Throwable"); + if(error instanceof Exception){ + throw ((Exception)error); + } + throw Exceptions.propagate(error); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxExpand.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxExpand.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxExpand.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2017-2021 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.ArrayDeque; +import java.util.Deque; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * A {@link Flux} that emits items from upstream and recursively expand them into + * inner sequences that are also replayed. The type of recursion is driven by the + * {@code breadthFirst} parameter. + * + * @param + * + * @author David Karnok + * @author Simon Baslé + */ +//adapted from RxJava2Extensions: https://github.com/akarnokd/RxJava2Extensions/blob/master/src/main/java/hu/akarnokd/rxjava2/operators/FlowableExpand.java +final class FluxExpand extends InternalFluxOperator { + + final boolean breadthFirst; + final Function> expander; + final int capacityHint; + + FluxExpand(Flux source, + Function> expander, + boolean breadthFirst, int capacityHint) { + super(source); + this.expander = expander; + this.breadthFirst = breadthFirst; + this.capacityHint = capacityHint; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber s) { + if (breadthFirst) { + ExpandBreathSubscriber parent = + new ExpandBreathSubscriber<>(s, expander, capacityHint); + parent.queue.offer(source); + s.onSubscribe(parent); + parent.drainQueue(); + } + else { + ExpandDepthSubscription parent = + new ExpandDepthSubscription<>(s, expander, capacityHint); + parent.source = source; + s.onSubscribe(parent); + } + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ExpandBreathSubscriber + extends Operators.MultiSubscriptionSubscriber { + + final Function> expander; + final Queue> queue; + + volatile boolean active; + volatile int wip; + + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ExpandBreathSubscriber.class, "wip"); + + long produced; + + ExpandBreathSubscriber(CoreSubscriber actual, + Function> expander, + int capacityHint) { + super(actual); + this.expander = expander; + this.queue = Queues.>unbounded(capacityHint).get(); + } + + @Override + public void onSubscribe(Subscription s) { + set(s); + } + + @Override + public void onNext(T t) { + produced++; + actual.onNext(t); + + Publisher p; + try { + p = Objects.requireNonNull(expander.apply(t), + "The expander returned a null Publisher"); + } + catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + super.cancel(); + actual.onError(ex); + drainQueue(); + return; + } + + queue.offer(p); + } + + @Override + public void onError(Throwable t) { + set(Operators.cancelledSubscription()); + super.cancel(); + actual.onError(t); + drainQueue(); + } + + @Override + public void onComplete() { + active = false; + drainQueue(); + } + + @Override + public void cancel() { + super.cancel(); + drainQueue(); + } + + void drainQueue() { + if (WIP.getAndIncrement(this) == 0) { + do { + Queue> q = queue; + if (isCancelled()) { + q.clear(); + } + else { + if (!active) { + if (q.isEmpty()) { + set(Operators.cancelledSubscription()); + super.cancel(); + actual.onComplete(); + } + else { + Publisher p = q.poll(); + long c = produced; + if (c != 0L) { + produced = 0L; + produced(c); + + } + active = true; + p.subscribe(this); + } + } + } + } + while (WIP.decrementAndGet(this) != 0); + } + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + } + + static final class ExpandDepthSubscription implements InnerProducer { + + final CoreSubscriber actual; + final Function> expander; + + volatile Throwable error; + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(ExpandDepthSubscription.class, Throwable.class, "error"); + + volatile int active; + static final AtomicIntegerFieldUpdater ACTIVE = + AtomicIntegerFieldUpdater.newUpdater(ExpandDepthSubscription.class, "active"); + + volatile long requested; + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ExpandDepthSubscription.class, "requested"); + + volatile Object current; + static final AtomicReferenceFieldUpdater CURRENT = + AtomicReferenceFieldUpdater.newUpdater(ExpandDepthSubscription.class, Object.class, "current"); + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ExpandDepthSubscription.class, "wip"); + + Deque> subscriptionStack; + + volatile boolean cancelled; + + CorePublisher source; + long consumed; + + ExpandDepthSubscription(CoreSubscriber actual, + Function> expander, + int capacityHint) { + this.actual = actual; + this.expander = expander; + this.subscriptionStack = new ArrayDeque<>(capacityHint); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drainQueue(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + Deque> q; + synchronized (this) { + q = subscriptionStack; + subscriptionStack = null; + } + + if (q != null) { + while (!q.isEmpty()) { + q.poll().dispose(); + } + } + + Object o = CURRENT.getAndSet(this, this); + if (o != this && o != null) { + ((ExpandDepthSubscriber) o).dispose(); + } + } + } + + @Nullable + ExpandDepthSubscriber pop() { + synchronized (this) { + Deque> q = subscriptionStack; + return q != null ? q.pollFirst() : null; + } + } + + boolean push(ExpandDepthSubscriber subscriber) { + synchronized (this) { + Deque> q = subscriptionStack; + if (q != null) { + q.offerFirst(subscriber); + return true; + } + return false; + } + } + + boolean setCurrent(ExpandDepthSubscriber inner) { + for (;;) { + Object o = CURRENT.get(this); + if (o == this) { + inner.dispose(); + return false; + } + if (CURRENT.compareAndSet(this, o, inner)) { + return true; + } + } + } + + void drainQueue() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + Subscriber a = actual; + long e = consumed; + + for (;;) { + Object o = current; + if (cancelled || o == this) { + source = null; + return; + } + + @SuppressWarnings("unchecked") + ExpandDepthSubscriber curr = (ExpandDepthSubscriber)o; + Publisher p = source; + + if (curr == null && p != null) { + source = null; + ACTIVE.getAndIncrement(this); + + ExpandDepthSubscriber eds = new ExpandDepthSubscriber<>(this); + if (setCurrent(eds)) { + p.subscribe(eds); + } + else { + return; + } + } + else if (curr == null) { + return; + } + else { + boolean currentDone = curr.done; + T v = curr.value; + + boolean newSource = false; + if (v != null && e != requested) { + curr.value = null; + a.onNext(v); + e++; + + try { + p = Objects.requireNonNull(expander.apply(v), + "The expander returned a null Publisher"); + } + catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + p = null; + curr.dispose(); + curr.done = true; + currentDone = true; + v = null; + Exceptions.addThrowable(ERROR, this, ex); + } + + if (p != null) { + if (push(curr)) { + ACTIVE.getAndIncrement(this); + curr = new ExpandDepthSubscriber<>(this); + if (setCurrent(curr)) { + p.subscribe(curr); + newSource = true; + } + else { + return; + } + } + } + } + + if (!newSource) { + if (currentDone && v == null) { + if (ACTIVE.decrementAndGet(this) == 0) { + Throwable ex = Exceptions.terminate(ERROR, this); + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + curr = pop(); + if (curr != null && setCurrent(curr)) { + curr.requestOne(); + continue; + } + else { + return; + } + } + } + } + int w = wip; + if (missed == w) { + consumed = e; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + else { + missed = w; + } + } + } + + void innerNext() { + drainQueue(); + } + + void innerError(ExpandDepthSubscriber inner, Throwable t) { + Exceptions.addThrowable(ERROR, this, t); + inner.done = true; + drainQueue(); + } + + void innerComplete(ExpandDepthSubscriber inner) { + inner.done = true; + drainQueue(); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return this.requested; + if (key == Attr.ERROR) return this.error; + + return InnerProducer.super.scanUnsafe(key); + } + } + + static final class ExpandDepthSubscriber implements InnerConsumer { + + ExpandDepthSubscription parent; + + volatile boolean done; + volatile T value; + + volatile Subscription s; + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(ExpandDepthSubscriber.class, Subscription.class, "s"); + + ExpandDepthSubscriber(ExpandDepthSubscription parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(1); + } + } + + @Override + public void onNext(T t) { + if (s != Operators.cancelledSubscription()) { + value = t; + parent.innerNext(); + } + } + + @Override + public void onError(Throwable t) { + if (s != Operators.cancelledSubscription()) { + parent.innerError(this, t); + } + } + + @Override + public void onComplete() { + if (s != Operators.cancelledSubscription()) { + parent.innerComplete(this); + } + } + + void requestOne() { + s.request(1); + } + + void dispose() { + Operators.terminate(S, this); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return parent.actual; + if (key == Attr.TERMINATED) return this.done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public Context currentContext() { + return parent.actual().currentContext(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxExtensions.kt =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxExtensions.kt (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxExtensions.kt (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +@file:Suppress("DEPRECATION") + +package reactor.core.publisher + +import org.reactivestreams.Publisher +import java.util.stream.Stream +import kotlin.reflect.KClass + + +/** + * Extension to convert any [Publisher] of [T] to a [Flux]. + * + * Note this extension doesn't make much sense on a [Flux] but it won't be converted so it + * doesn't hurt. + * + * @author Simon Baslé + * @since 3.1.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun Publisher.toFlux(): Flux = Flux.from(this) + +/** + * Extension for transforming an [Iterator] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun Iterator.toFlux(): Flux = toIterable().toFlux() + +/** + * Extension for transforming an [Iterable] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun Iterable.toFlux(): Flux = Flux.fromIterable(this) + +/** + * Extension for transforming a [Sequence] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun Sequence.toFlux(): Flux = Flux.fromIterable(object : Iterable { + override fun iterator(): Iterator = this@toFlux.iterator() +}) + +/** + * Extension for transforming a [Stream] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun Stream.toFlux(): Flux = Flux.fromStream(this) + +/** + * Extension for transforming a [BooleanArray] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun BooleanArray.toFlux(): Flux = this.toList().toFlux() + +/** + * Extension for transforming a [ByteArray] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun ByteArray.toFlux(): Flux = this.toList().toFlux() + +/** + * Extension for transforming a [ShortArray] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun ShortArray.toFlux(): Flux = this.toList().toFlux() + +/** + * Extension for transforming a [IntArray] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun IntArray.toFlux(): Flux = this.toList().toFlux() + +/** + * Extension for transforming a [LongArray] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun LongArray.toFlux(): Flux = this.toList().toFlux() + +/** + * Extension for transforming a [FloatArray] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun FloatArray.toFlux(): Flux = this.toList().toFlux() + +/** + * Extension for transforming a [DoubleArray] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun DoubleArray.toFlux(): Flux = this.toList().toFlux() + +/** + * Extension for transforming an [Array] to a [Flux]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun Array.toFlux(): Flux = Flux.fromArray(this) + +private fun Iterator.toIterable() = object : Iterable { + override fun iterator(): Iterator = this@toIterable +} + +/** + * Extension for transforming an exception to a [Flux] that completes with the specified error. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toFlux()", "reactor.kotlin.core.publisher.toFlux")) +fun Throwable.toFlux(): Flux = Flux.error(this) + +/** + * Extension for [Flux.cast] providing a `cast()` variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("cast()", "reactor.kotlin.core.publisher.cast")) +inline fun Flux<*>.cast(): Flux = cast(T::class.java) + + +/** + * Extension for [Flux.doOnError] providing a [KClass] based variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("doOnError(exceptionType, onError)", "reactor.kotlin.core.publisher.doOnError")) +fun Flux.doOnError(exceptionType: KClass, onError: (E) -> Unit): Flux = + doOnError(exceptionType.java) { onError(it) } + +/** + * Extension for [Flux.onErrorMap] providing a [KClass] based variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("onErrorMap(exceptionType, mapper)", "reactor.kotlin.core.publisher.onErrorMap")) +fun Flux.onErrorMap(exceptionType: KClass, mapper: (E) -> Throwable): Flux = + onErrorMap(exceptionType.java) { mapper(it) } + +/** + * Extension for [Flux.ofType] providing a `ofType()` variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("ofType()", "reactor.kotlin.core.publisher.ofType")) +inline fun Flux<*>.ofType(): Flux = ofType(T::class.java) + +/** + * Extension for [Flux.onErrorResume] providing a [KClass] based variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("onErrorResume(exceptionType, fallback)", "reactor.kotlin.core.publisher.onErrorResume")) +fun Flux.onErrorResume(exceptionType: KClass, fallback: (E) -> Publisher): Flux = + onErrorResume(exceptionType.java) { fallback(it) } + +/** + * Extension for [Flux.onErrorReturn] providing a [KClass] based variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("onErrorReturn(exceptionType, value)", "reactor.kotlin.core.publisher.onErrorReturn")) +fun Flux.onErrorReturn(exceptionType: KClass, value: T): Flux = + onErrorReturn(exceptionType.java, value) + +/** + * Extension for flattening [Flux] of [Iterable] + * + * @author Igor Perikov + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("split()", "reactor.kotlin.core.publisher.split")) +fun Flux>.split(): Flux = this.flatMapIterable { it } + +/** + * Extension for [Flux.switchIfEmpty] accepting a function providing a Publisher. This allows having a deferred execution with + * the [switchIfEmpty] operator + * + * @author Kevin Davin + * @since 3.2 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("switchIfEmpty(s)", "reactor.kotlin.core.publisher.switchIfEmpty")) +fun Flux.switchIfEmpty(s: () -> Publisher): Flux = this.switchIfEmpty(Flux.defer { s() }) Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFilter.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFilter.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFilter.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Filters out values that make a filter function return false. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxFilter extends InternalFluxOperator { + + final Predicate predicate; + + FluxFilter(Flux source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new FilterConditionalSubscriber<>((ConditionalSubscriber) actual, + predicate); + } + return new FilterSubscriber<>(actual, predicate); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class FilterSubscriber + implements InnerOperator, + Fuseable.ConditionalSubscriber { + + final CoreSubscriber actual; + final Context ctx; + + final Predicate predicate; + + Subscription s; + + boolean done; + + FilterSubscriber(CoreSubscriber actual, Predicate predicate) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.predicate = predicate; + } + + @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, this.ctx); + return; + } + + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, this.ctx, s); + if (e_ != null) { + onError(e_); + } + else { + s.request(1); + } + Operators.onDiscard(t, this.ctx); + return; + } + if (b) { + actual.onNext(t); + } + else { + Operators.onDiscard(t, this.ctx); + s.request(1); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, this.ctx); + return false; + } + + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, this.ctx, s); + if (e_ != null) { + onError(e_); + } + Operators.onDiscard(t, this.ctx); + return false; + } + if (b) { + actual.onNext(t); + } + else { + Operators.onDiscard(t, this.ctx); + } + return b; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, this.ctx); + return; + } + done = true; + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + actual.onComplete(); + } + + @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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } + + static final class FilterConditionalSubscriber + implements InnerOperator, + Fuseable.ConditionalSubscriber { + + final Fuseable.ConditionalSubscriber actual; + final Context ctx; + + final Predicate predicate; + + Subscription s; + + boolean done; + + FilterConditionalSubscriber(Fuseable.ConditionalSubscriber actual, + Predicate predicate) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.predicate = predicate; + } + + @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, this.ctx); + return; + } + + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, this.ctx, s); + if (e_ != null) { + onError(e_); + } + else { + s.request(1); + } + Operators.onDiscard(t, this.ctx); + return; + } + if (b) { + actual.onNext(t); + } + else { + s.request(1); + Operators.onDiscard(t, this.ctx); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, this.ctx); + return false; + } + + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, this.ctx, s); + if (e_ != null) { + onError(e_); + } + Operators.onDiscard(t, this.ctx); + return false; + } + if (b) { + return actual.tryOnNext(t); + } + else { + Operators.onDiscard(t, this.ctx); + return false; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, this.ctx); + return; + } + done = true; + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + actual.onComplete(); + } + + @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); + } + + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFilterFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFilterFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFilterFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,497 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Filters out values that make a filter function return false. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxFilterFuseable extends InternalFluxOperator implements Fuseable { + + final Predicate predicate; + + FluxFilterFuseable(Flux source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new FilterFuseableConditionalSubscriber<>((ConditionalSubscriber) actual, + predicate); + } + return new FilterFuseableSubscriber<>(actual, predicate); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class FilterFuseableSubscriber + implements InnerOperator, QueueSubscription, + ConditionalSubscriber { + + final CoreSubscriber actual; + final Context ctx; + + final Predicate predicate; + + QueueSubscription s; + + boolean done; + + int sourceMode; + + FilterFuseableSubscriber(CoreSubscriber actual, + Predicate predicate) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.predicate = predicate; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = (QueueSubscription) s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, this.ctx); + return; + } + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, this.ctx, s); + if (e_ != null) { + onError(e_); + } + else { + s.request(1); + } + Operators.onDiscard(t, this.ctx); + return; + } + if (b) { + actual.onNext(t); + } + else { + s.request(1); + Operators.onDiscard(t, this.ctx); + } + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, this.ctx); + return false; + } + + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, this.ctx, s); + if (e_ != null) { + onError(e_); + } + Operators.onDiscard(t, this.ctx); + return false; + } + if (b) { + actual.onNext(t); + return true; + } + Operators.onDiscard(t, this.ctx); + return false; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, this.ctx); + return; + } + done = true; + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + actual.onComplete(); + } + + @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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + @Nullable + public T poll() { + if (sourceMode == ASYNC) { + long dropped = 0; + for (; ; ) { + T v = s.poll(); + + try { + if (v == null || predicate.test(v)) { + if (dropped != 0) { + request(dropped); + } + return v; + } + Operators.onDiscard(v, this.ctx); + dropped++; + } + catch (Throwable e) { + RuntimeException e_ = Operators.onNextPollError(v, e, currentContext()); + Operators.onDiscard(v, this.ctx); + if (e_ != null) { + throw e_; + } + //else continue + } + } + } + else { + for (; ; ) { + T v = s.poll(); + + try { + if (v == null || predicate.test(v)) { + return v; + } + Operators.onDiscard(v, this.ctx); + } + catch (Throwable e) { + RuntimeException e_ = Operators.onNextPollError(v, e, currentContext()); + Operators.onDiscard(v, this.ctx); + if (e_ != null) { + throw e_; + } + // else continue + } + } + } + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + } + + static final class FilterFuseableConditionalSubscriber + implements InnerOperator, ConditionalSubscriber, + QueueSubscription { + + final ConditionalSubscriber actual; + final Context ctx; + + final Predicate predicate; + + QueueSubscription s; + + boolean done; + + int sourceMode; + + FilterFuseableConditionalSubscriber(ConditionalSubscriber actual, + Predicate predicate) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.predicate = predicate; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = (QueueSubscription) s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, this.ctx); + return; + } + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, this.ctx, s); + if (e_ != null) { + onError(e_); + } + else { + s.request(1); + } + Operators.onDiscard(t, this.ctx); + return; + } + if (b) { + actual.onNext(t); + } + else { + s.request(1); + Operators.onDiscard(t, this.ctx); + } + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, this.ctx); + return false; + } + + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, this.ctx, s); + if (e_ != null) { + onError(e_); + } + Operators.onDiscard(t, this.ctx); + return false; + } + if (b) { + return actual.tryOnNext(t); + } + else { + Operators.onDiscard(t, this.ctx); + return false; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, this.ctx); + return; + } + done = true; + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + actual.onComplete(); + } + + @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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + @Nullable + public T poll() { + if (sourceMode == ASYNC) { + long dropped = 0; + for (; ; ) { + T v = s.poll(); + try { + if (v == null || predicate.test(v)) { + if (dropped != 0) { + request(dropped); + } + return v; + } + Operators.onDiscard(v, this.ctx); + dropped++; + } + catch (Throwable e) { + RuntimeException e_ = Operators.onNextPollError(v, e, this.ctx); + Operators.onDiscard(v, this.ctx); + if (e_ != null) { + throw e_; + } + // else continue + } + } + } + else { + for (; ; ) { + T v = s.poll(); + + try { + if (v == null || predicate.test(v)) { + return v; + } + Operators.onDiscard(v, this.ctx); + } + catch (Throwable e) { + RuntimeException e_ = Operators.onNextPollError(v, e, this.ctx); + Operators.onDiscard(v, this.ctx); + if (e_ != null) { + throw e_; + } + // else continue + } + } + } + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int size() { + return s.size(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFilterWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFilterWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFilterWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2017-2021 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.Callable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.stream.Stream; + +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.concurrent.Queues; +import reactor.util.context.Context; + + +/** + * Maps each upstream value into a single {@code true} or {@code false} value provided by + * a generated Publisher for that input value and emits the input value if the inner + * Publisher returned {@code true}. + *

+ * Only the first item emitted by the inner Publisher's are considered. If + * the inner Publisher is empty, no resulting item is generated for that input value. + * + * @param the input value type + * + * @author David Karnok + * @author Simon Baslé + */ +//adapted from RxJava2Extensions: https://github.com/akarnokd/RxJava2Extensions/blob/master/src/main/java/hu/akarnokd/rxjava2/operators/FlowableFilterAsync.java +class FluxFilterWhen extends InternalFluxOperator { + + final Function> asyncPredicate; + + final int bufferSize; + + FluxFilterWhen(Flux source, + Function> asyncPredicate, + int bufferSize) { + super(source); + this.asyncPredicate = asyncPredicate; + this.bufferSize = bufferSize; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxFilterWhenSubscriber<>(actual, asyncPredicate, bufferSize); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class FluxFilterWhenSubscriber implements InnerOperator { + + final Function> asyncPredicate; + final int bufferSize; + final AtomicReferenceArray toFilter; + final CoreSubscriber actual; + final Context ctx; + + int consumed; + long consumerIndex; + long emitted; + Boolean innerResult; + long producerIndex; + Subscription upstream; + + volatile boolean cancelled; + volatile FilterWhenInner current; + volatile boolean done; + volatile Throwable error; + volatile long requested; + volatile int state; + volatile int wip; + + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(FluxFilterWhenSubscriber.class, Throwable.class, "error"); + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(FluxFilterWhenSubscriber.class, "requested"); + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(FluxFilterWhenSubscriber.class, "wip"); + static final AtomicReferenceFieldUpdaterCURRENT = + AtomicReferenceFieldUpdater.newUpdater(FluxFilterWhenSubscriber.class, FilterWhenInner.class, "current"); + + @SuppressWarnings("ConstantConditions") + static final FilterWhenInner INNER_CANCELLED = new FilterWhenInner(null, false); + + static final int STATE_FRESH = 0; + static final int STATE_RUNNING = 1; + static final int STATE_RESULT = 2; + + FluxFilterWhenSubscriber(CoreSubscriber actual, + Function> asyncPredicate, + int bufferSize) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.toFilter = new AtomicReferenceArray<>(Queues.ceilingNextPowerOfTwo(bufferSize)); + this.asyncPredicate = asyncPredicate; + this.bufferSize = bufferSize; + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public void onNext(T t) { + long pi = producerIndex; + int m = toFilter.length() - 1; + + int offset = (int)pi & m; + toFilter.lazySet(offset, t); + producerIndex = pi + 1; + drain(); + } + + @Override + public void onError(Throwable t) { + ERROR.set(this, t); + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + upstream.cancel(); + cancelInner(); + if (WIP.getAndIncrement(this) == 0) { + clear(); + } + } + } + + void cancelInner() { + FilterWhenInner a = CURRENT.get(this); + if (a != INNER_CANCELLED) { + a = CURRENT.getAndSet(this, INNER_CANCELLED); + if (a != null && a != INNER_CANCELLED) { + a.cancel(); + } + } + } + + void clear() { + int n = toFilter.length(); + for (int i = 0; i < n; i++) { + T old = toFilter.getAndSet(i, null); + Operators.onDiscard(old, ctx); + } + innerResult = null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(upstream, s)) { + upstream = s; + actual.onSubscribe(this); + s.request(bufferSize); + } + } + + @SuppressWarnings("unchecked") + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + int limit = Operators.unboundedOrLimit(bufferSize); + long e = emitted; + long ci = consumerIndex; + int f = consumed; + int m = toFilter.length() - 1; + Subscriber a = actual; + + for (;;) { + long r = requested; + + while (e != r) { + if (cancelled) { + clear(); + return; + } + + boolean d = done; + + int offset = (int)ci & m; + T t = toFilter.get(offset); + boolean empty = t == null; + + if (d && empty) { + Throwable ex = Exceptions.terminate(ERROR, this); + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; + } + + if (empty) { + break; + } + + int s = state; + if (s == STATE_FRESH) { + Publisher p; + + try { + p = Objects.requireNonNull(asyncPredicate.apply(t), "The asyncPredicate returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + Exceptions.addThrowable(ERROR, this, ex); + p = null; //discarded as "old" below + } + + if (p != null) { + if (p instanceof Callable) { + Boolean u; + + try { + u = ((Callable)p).call(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + Exceptions.addThrowable(ERROR, this, ex); + u = null; //triggers discard below + } + + if (u != null && u) { + a.onNext(t); + e++; + } + else { + Operators.onDiscard(t, ctx); + } + } else { + FilterWhenInner inner = new FilterWhenInner(this, !(p instanceof Mono)); + if (CURRENT.compareAndSet(this,null, inner)) { + state = STATE_RUNNING; + p.subscribe(inner); + break; + } + } + } + + T old = toFilter.getAndSet(offset, null); + Operators.onDiscard(old, ctx); + ci++; + if (++f == limit) { + f = 0; + upstream.request(limit); + } + } else + if (s == STATE_RESULT) { + Boolean u = innerResult; + innerResult = null; + + if (u != null && u) { + a.onNext(t); + e++; + } + else { + Operators.onDiscard(t, ctx); + } + + toFilter.lazySet(offset, null); + ci++; + if (++f == limit) { + f = 0; + upstream.request(limit); + } + state = STATE_FRESH; + } else { + break; + } + } + + if (e == r) { + if (cancelled) { + clear(); + return; + } + + boolean d = done; + + int offset = (int)ci & m; + T t = toFilter.get(offset); + boolean empty = t == null; + + if (d && empty) { + Throwable ex = Exceptions.terminate(ERROR, this); + if (ex == null) { + a.onComplete(); + } else { + a.onError(ex); + } + return; + } + } + + int w = wip; + if (missed == w) { + consumed = f; + consumerIndex = ci; + emitted = e; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + + void clearCurrent() { + FilterWhenInner c = current; + if (c != INNER_CANCELLED) { + CURRENT.compareAndSet(this, c, null); + } + } + + void innerResult(Boolean item) { + innerResult = item; + state = STATE_RESULT; + clearCurrent(); + drain(); + } + + void innerError(Throwable ex) { + Exceptions.addThrowable(ERROR, this, ex); + state = STATE_RESULT; + clearCurrent(); + drain(); + } + + void innerComplete() { + state = STATE_RESULT; + clearCurrent(); + drain(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return upstream; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.ERROR) //FIXME ERROR is often reset by Exceptions.terminate :( + return error; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.CAPACITY) return toFilter.length(); + if (key == Attr.LARGE_BUFFERED) return producerIndex - consumerIndex; + if (key == Attr.BUFFERED) { + long realBuffered = producerIndex - consumerIndex; + if (realBuffered <= Integer.MAX_VALUE) return (int) realBuffered; + return Integer.MIN_VALUE; + } + if (key == Attr.PREFETCH) return bufferSize; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + FilterWhenInner c = current; + return c == null ? Stream.empty() : Stream.of(c); + } + } + + static final class FilterWhenInner implements InnerConsumer { + + final FluxFilterWhenSubscriber parent; + final boolean cancelOnNext; + + boolean done; + + volatile Subscription sub; + + static final AtomicReferenceFieldUpdater SUB = + AtomicReferenceFieldUpdater.newUpdater(FilterWhenInner.class, Subscription.class, "sub"); + + FilterWhenInner(FluxFilterWhenSubscriber parent, boolean cancelOnNext) { + this.parent = parent; + this.cancelOnNext = cancelOnNext; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(SUB, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Boolean t) { + if (!done) { + if (cancelOnNext) { + sub.cancel(); + } + done = true; + parent.innerResult(t); + } + } + + @Override + public void onError(Throwable t) { + if (!done) { + done = true; + parent.innerError(t); + } else { + Operators.onErrorDropped(t, parent.currentContext()); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + parent.innerComplete(); + } + } + + void cancel() { + Operators.terminate(SUB, this); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.ACTUAL) return sub; + if (key == Attr.CANCELLED) return sub == Operators.cancelledSubscription(); + if (key == Attr.TERMINATED) return done; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return done ? 0L : 1L; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFirstWithSignal.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFirstWithSignal.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFirstWithSignal.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Given a set of source Publishers the values of that Publisher is forwarded to the + * subscriber which responds first with any signal. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxFirstWithSignal extends Flux implements SourceProducer { + + final Publisher[] array; + + final Iterable> iterable; + + @SafeVarargs + FluxFirstWithSignal(Publisher... array) { + this.array = Objects.requireNonNull(array, "array"); + this.iterable = null; + } + + FluxFirstWithSignal(Iterable> iterable) { + this.array = null; + this.iterable = Objects.requireNonNull(iterable); + } + + @SuppressWarnings("unchecked") + @Override + public void subscribe(CoreSubscriber actual) { + Publisher[] a = array; + int n; + if (a == null) { + n = 0; + a = new Publisher[8]; + + Iterator> it; + + try { + it = Objects.requireNonNull(iterable.iterator(), + "The iterator returned is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + for (; ; ) { + + boolean b; + + try { + b = it.hasNext(); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (!b) { + break; + } + + Publisher p; + + try { + p = Objects.requireNonNull(it.next(), + "The Publisher returned by the iterator is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (n == a.length) { + Publisher[] c = new Publisher[n + (n >> 2)]; + System.arraycopy(a, 0, c, 0, n); + a = c; + } + a[n++] = p; + } + + } + else { + n = a.length; + } + + if (n == 0) { + Operators.complete(actual); + return; + } + if (n == 1) { + Publisher p = a[0]; + + if (p == null) { + Operators.error(actual, + new NullPointerException("The single source Publisher is null")); + } + else { + p.subscribe(actual); + } + return; + } + + RaceCoordinator coordinator = new RaceCoordinator<>(n); + + coordinator.subscribe(a, n, actual); + } + + /** + * Returns a new instance which has the additional source to be amb'd together with + * the current array of sources. + *

+ * This operation doesn't change the current {@link FluxFirstWithSignal} instance. + * + * @param source the new source to merge with the others + * + * @return the new {@link FluxFirstWithSignal} instance or null if the Amb runs with an Iterable + */ + @Nullable + FluxFirstWithSignal orAdditionalSource(Publisher source) { + if (array != null) { + int n = array.length; + @SuppressWarnings("unchecked") Publisher[] newArray = + new Publisher[n + 1]; + System.arraycopy(array, 0, newArray, 0, n); + newArray[n] = source; + + return new FluxFirstWithSignal<>(newArray); + } + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class RaceCoordinator + implements Subscription, Scannable { + + final FirstEmittingSubscriber[] subscribers; + + volatile boolean cancelled; + + volatile int winner; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WINNER = + AtomicIntegerFieldUpdater.newUpdater(RaceCoordinator.class, "winner"); + + @SuppressWarnings("unchecked") + RaceCoordinator(int n) { + subscribers = new FirstEmittingSubscriber[n]; + winner = Integer.MIN_VALUE; + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + + return null; + } + + void subscribe(Publisher[] sources, + int n, + CoreSubscriber actual) { + FirstEmittingSubscriber[] a = subscribers; + + for (int i = 0; i < n; i++) { + a[i] = new FirstEmittingSubscriber<>(actual, this, i); + } + + actual.onSubscribe(this); + + for (int i = 0; i < n; i++) { + if (cancelled || winner != Integer.MIN_VALUE) { + return; + } + + Publisher p = sources[i]; + + if (p == null) { + if (WINNER.compareAndSet(this, Integer.MIN_VALUE, -1)) { + actual.onError(new NullPointerException("The " + i + " th Publisher source is null")); + } + return; + } + + p.subscribe(a[i]); + } + + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + int w = winner; + if (w >= 0) { + subscribers[w].request(n); + } + else { + for (FirstEmittingSubscriber s : subscribers) { + s.request(n); + } + } + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + + int w = winner; + if (w >= 0) { + subscribers[w].cancel(); + } + else { + for (FirstEmittingSubscriber s : subscribers) { + s.cancel(); + } + } + } + + boolean tryWin(int index) { + if (winner == Integer.MIN_VALUE) { + if (WINNER.compareAndSet(this, Integer.MIN_VALUE, index)) { + + FirstEmittingSubscriber[] a = subscribers; + int n = a.length; + + for (int i = 0; i < n; i++) { + if (i != index) { + a[i].cancel(); + } + } + + return true; + } + } + return false; + } + } + + static final class FirstEmittingSubscriber extends Operators.DeferredSubscription + implements InnerOperator { + + final RaceCoordinator parent; + + final CoreSubscriber actual; + + final int index; + + boolean won; + + FirstEmittingSubscriber(CoreSubscriber actual, + RaceCoordinator parent, + int index) { + this.actual = actual; + this.parent = parent; + this.index = index; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return parent.cancelled; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + set(s); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onNext(T t) { + if (won) { + actual.onNext(t); + } + else if (parent.tryWin(index)) { + won = true; + actual.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + if (won) { + actual.onError(t); + } + else if (parent.tryWin(index)) { + won = true; + actual.onError(t); + } + } + + @Override + public void onComplete() { + if (won) { + actual.onComplete(); + } + else if (parent.tryWin(index)) { + won = true; + actual.onComplete(); + } + } + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFirstWithValue.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFirstWithValue.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFirstWithValue.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2016-2021 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.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.stream.Stream; + +/** + * Given a set of source Publishers the values of that Publisher is forwarded to the + * subscriber which responds first with a value. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxFirstWithValue extends Flux implements SourceProducer { + + final Publisher[] array; + + final Iterable> iterable; + + private FluxFirstWithValue(Publisher[] array) { + this.array = Objects.requireNonNull(array, "array"); + this.iterable = null; + } + + @SafeVarargs + FluxFirstWithValue(Publisher first, Publisher... others) { + Objects.requireNonNull(first, "first"); + Objects.requireNonNull(others, "others"); + @SuppressWarnings("unchecked") + Publisher[] newArray = new Publisher[others.length + 1]; + newArray[0] = first; + System.arraycopy(others, 0, newArray, 1, others.length); + this.array = newArray; + this.iterable = null; + } + + FluxFirstWithValue(Iterable> iterable) { + this.array = null; + this.iterable = Objects.requireNonNull(iterable); + } + + /** + * Returns a new instance which has the additional sources to be flattened together with + * the current array of sources. + *

+ * This operation doesn't change the current {@link FluxFirstWithValue} instance. + * + * @param others the new sources to merge with the current sources + * + * @return the new {@link FluxFirstWithValue} instance or null if new sources cannot be added (backed by an Iterable) + */ + @SafeVarargs + @Nullable + final FluxFirstWithValue firstValuedAdditionalSources(Publisher... others) { + Objects.requireNonNull(others, "others"); + if (others.length == 0) { + return this; + } + if (array == null) { + //iterable mode, returning null to convey 2 nested operators are needed here + return null; + } + int currentSize = array.length; + int otherSize = others.length; + @SuppressWarnings("unchecked") + Publisher[] newArray = new Publisher[currentSize + otherSize]; + System.arraycopy(array, 0, newArray, 0, currentSize); + System.arraycopy(others, 0, newArray, currentSize, otherSize); + + return new FluxFirstWithValue<>(newArray); + } + + @SuppressWarnings("unchecked") + @Override + public void subscribe(CoreSubscriber actual) { + Publisher[] a = array; + int n; + if (a == null) { + n = 0; + a = new Publisher[8]; + + Iterator> it; + + try { + it = Objects.requireNonNull(iterable.iterator(), + "The iterator returned is null"); + } catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + for (; ; ) { + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (!b) { + break; + } + + Publisher p; + + try { + p = Objects.requireNonNull(it.next(), + "The Publisher returned by the iterator is null"); + } catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (n == a.length) { + Publisher[] c = new Publisher[n + (n >> 2)]; + System.arraycopy(a, 0, c, 0, n); + a = c; + } + a[n++] = p; + } + + } else { + n = a.length; + } + + if (n == 0) { + Operators.complete(actual); + return; + } + if (n == 1) { + Publisher p = a[0]; + + if (p == null) { + Operators.error(actual, + new NullPointerException("The single source Publisher is null")); + } else { + p.subscribe(actual); + } + return; + } + + RaceValuesCoordinator coordinator = new RaceValuesCoordinator<>(n); + + coordinator.subscribe(a, n, actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class RaceValuesCoordinator + implements Subscription, Scannable { + + final FirstValuesEmittingSubscriber[] subscribers; + final Throwable[] errorsOrCompleteEmpty; + + volatile boolean cancelled; + + @SuppressWarnings("rawtypes") + volatile int winner; + static final AtomicIntegerFieldUpdater WINNER = + AtomicIntegerFieldUpdater.newUpdater(RaceValuesCoordinator.class, "winner"); + + @SuppressWarnings("rawtypes") + volatile int nbErrorsOrCompletedEmpty; + static final AtomicIntegerFieldUpdater ERRORS_OR_COMPLETED_EMPTY = + AtomicIntegerFieldUpdater.newUpdater(RaceValuesCoordinator.class, "nbErrorsOrCompletedEmpty"); + + @SuppressWarnings("unchecked") + public RaceValuesCoordinator(int n) { + subscribers = new FirstValuesEmittingSubscriber[n]; + errorsOrCompleteEmpty = new Throwable[n]; + winner = Integer.MIN_VALUE; + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + + return null; + } + + void subscribe(Publisher[] sources, + int n, CoreSubscriber actual) { + + for (int i = 0; i < n; i++) { + subscribers[i] = new FirstValuesEmittingSubscriber(actual, this, i); + } + + actual.onSubscribe(this); + + for (int i = 0; i < n; i++) { + if (cancelled || winner != Integer.MIN_VALUE) { + return; + } + + if (sources[i] == null) { + actual.onError(new NullPointerException("The " + i + " th Publisher source is null")); + return; + } + + sources[i].subscribe(subscribers[i]); + } + } + + + @Override + public void request(long n) { + if (Operators.validate(n)) { + int w = winner; + if (w >= 0) { + subscribers[w].request(n); + } else { + for (FirstValuesEmittingSubscriber s : subscribers) { + s.request(n); + } + } + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + + int w = winner; + if (w >= 0) { + subscribers[w].cancel(); + } else { + for (FirstValuesEmittingSubscriber s : subscribers) { + s.cancel(); + } + } + } + + boolean tryWin(int index) { + if (winner == Integer.MIN_VALUE) { + if (WINNER.compareAndSet(this, Integer.MIN_VALUE, index)) { + for (int i = 0; i < subscribers.length; i++) { + if (i != index) { + subscribers[i].cancel(); + errorsOrCompleteEmpty[i] = null; + } + } + return true; + } + } + return false; + } + + } + + static final class FirstValuesEmittingSubscriber extends Operators.DeferredSubscription + implements InnerOperator { + + final RaceValuesCoordinator parent; + + final CoreSubscriber actual; + + final int index; + + boolean won; + + FirstValuesEmittingSubscriber(CoreSubscriber actual, RaceValuesCoordinator parent, int index) { + this.actual = actual; + this.parent = parent; + this.index = index; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return parent.cancelled; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + set(s); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onNext(T t) { + if (won) { + actual.onNext(t); + } else if (parent.tryWin(index)) { + won = true; + actual.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + if (won) { + actual.onError(t); + } else { + recordTerminalSignals(t); + } + } + + @Override + public void onComplete() { + if (won) { + actual.onComplete(); + } else { + recordTerminalSignals(new NoSuchElementException("source at index " + index + " completed empty")); + } + } + + void recordTerminalSignals(Throwable t) { + parent.errorsOrCompleteEmpty[index] = t; + int nb = RaceValuesCoordinator.ERRORS_OR_COMPLETED_EMPTY.incrementAndGet(parent); + + if (nb == parent.subscribers.length) { + NoSuchElementException e = new NoSuchElementException("All sources completed with error or without values"); + e.initCause(Exceptions.multiple(parent.errorsOrCompleteEmpty)); + actual.onError(e); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFlatMap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFlatMap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFlatMap.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,1167 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +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; + +/** + * Maps a sequence of values each into a Publisher and flattens them + * back into a single sequence, interleaving events from the various inner Publishers. + * + * @param the source value type + * @param the result value type + * @see Reactive-Streams-Commons + */ +final class FluxFlatMap extends InternalFluxOperator { + + final Function> mapper; + + final boolean delayError; + + final int maxConcurrency; + + final Supplier> mainQueueSupplier; + + final int prefetch; + + final Supplier> innerQueueSupplier; + + FluxFlatMap(Flux source, + Function> mapper, + boolean delayError, + int maxConcurrency, + Supplier> mainQueueSupplier, + int prefetch, + Supplier> innerQueueSupplier) { + super(source); + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + if (maxConcurrency <= 0) { + throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); + } + this.mapper = Objects.requireNonNull(mapper, "mapper"); + this.delayError = delayError; + this.prefetch = prefetch; + this.maxConcurrency = maxConcurrency; + this.mainQueueSupplier = + Objects.requireNonNull(mainQueueSupplier, "mainQueueSupplier"); + this.innerQueueSupplier = + Objects.requireNonNull(innerQueueSupplier, "innerQueueSupplier"); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (trySubscribeScalarMap(source, actual, mapper, false, true)) { + return null; + } + + return new FlatMapMain<>(actual, + mapper, + delayError, + maxConcurrency, + mainQueueSupplier, + prefetch, innerQueueSupplier); + } + + /** + * Checks if the source is a Supplier and if the mapper's publisher output is also + * a supplier, thus avoiding subscribing to any of them. + * + * @param source the source publisher + * @param s the end consumer + * @param mapper the mapper function + * @param fuseableExpected if true, the parent class was marked Fuseable thus the + * mapping output has to signal onSubscribe with a QueueSubscription + * + * @return true if the optimization worked + */ + @SuppressWarnings("unchecked") + static boolean trySubscribeScalarMap(Publisher source, + CoreSubscriber s, + Function> mapper, + boolean fuseableExpected, + boolean errorContinueExpected) { + if (source instanceof Callable) { + T t; + + try { + t = ((Callable) source).call(); + } + catch (Throwable e) { + Context ctx = s.currentContext(); + Throwable e_ = errorContinueExpected ? + Operators.onNextError(null, e, ctx) : + Operators.onOperatorError(e, ctx); + if (e_ != null) { + Operators.error(s, e_); + } + else { + //the error was recovered but we know there won't be any more value + Operators.complete(s); + } + return true; + } + + if (t == null) { + Operators.complete(s); + return true; + } + + Publisher p; + + try { + p = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null Publisher"); + } + catch (Throwable e) { + Context ctx = s.currentContext(); + Throwable e_ = errorContinueExpected ? + Operators.onNextError(t, e, ctx) : + Operators.onOperatorError(null, e, t, ctx); + if (e_ != null) { + Operators.error(s, e_); + } + else { + //the error was recovered but we know there won't be any more value + Operators.complete(s); + } + return true; + } + + if (p instanceof Callable) { + R v; + + try { + v = ((Callable) p).call(); + } + catch (Throwable e) { + Context ctx = s.currentContext(); + Throwable e_ = errorContinueExpected ? + Operators.onNextError(t, e, ctx) : + Operators.onOperatorError(null, e, t, ctx); + if (e_ != null) { + Operators.error(s, e_); + } + else { + //the error was recovered but we know there won't be any more value + Operators.complete(s); + } + return true; + } + + if (v != null) { + s.onSubscribe(Operators.scalarSubscription(s, v)); + } + else { + Operators.complete(s); + } + } + else { + if (!fuseableExpected || p instanceof Fuseable) { + p.subscribe(s); + } + else { + p.subscribe(new FluxHide.SuppressFuseableSubscriber<>(s)); + } + } + + return true; + } + + return false; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class FlatMapMain extends FlatMapTracker> + implements InnerOperator { + + final boolean delayError; + final int maxConcurrency; + final int prefetch; + final int limit; + final Function> mapper; + final Supplier> mainQueueSupplier; + final Supplier> innerQueueSupplier; + final CoreSubscriber actual; + + volatile Queue scalarQueue; + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(FlatMapMain.class, + Throwable.class, + "error"); + + volatile boolean done; + + volatile boolean cancelled; + + Subscription s; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(FlatMapMain.class, "requested"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(FlatMapMain.class, "wip"); + + @SuppressWarnings("rawtypes") + static final FlatMapInner[] EMPTY = new FlatMapInner[0]; + + @SuppressWarnings("rawtypes") + static final FlatMapInner[] TERMINATED = new FlatMapInner[0]; + + + int lastIndex; + + int produced; + + FlatMapMain(CoreSubscriber actual, + Function> mapper, + boolean delayError, + int maxConcurrency, + Supplier> mainQueueSupplier, + int prefetch, + Supplier> innerQueueSupplier) { + this.actual = actual; + this.mapper = mapper; + this.delayError = delayError; + this.maxConcurrency = maxConcurrency; + this.mainQueueSupplier = mainQueueSupplier; + this.prefetch = prefetch; + this.innerQueueSupplier = innerQueueSupplier; + this.limit = Operators.unboundedOrLimit(maxConcurrency); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public Stream inners() { + return Stream.of(array).filter(Objects::nonNull); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.ERROR) return error; + if (key == Attr.TERMINATED) return done && (scalarQueue == null || scalarQueue.isEmpty()); + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.PREFETCH) return maxConcurrency; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.LARGE_BUFFERED) return (scalarQueue != null ? (long) scalarQueue.size() : 0L) + size; + if (key == Attr.BUFFERED) { + long realBuffered = (scalarQueue != null ? (long) scalarQueue.size() : 0L) + size; + if (realBuffered <= Integer.MAX_VALUE) return (int) realBuffered; + return Integer.MIN_VALUE; + } + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @SuppressWarnings("unchecked") + @Override + FlatMapInner[] empty() { + return EMPTY; + } + + @SuppressWarnings("unchecked") + @Override + FlatMapInner[] terminated() { + return TERMINATED; + } + + @SuppressWarnings("unchecked") + @Override + FlatMapInner[] newArray(int size) { + return new FlatMapInner[size]; + } + + @Override + void setIndex(FlatMapInner entry, int index) { + entry.index = index; + } + + @Override + void unsubscribeEntry(FlatMapInner entry) { + entry.cancel(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(null); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + if (WIP.getAndIncrement(this) == 0) { + Operators.onDiscardQueueWithClear(scalarQueue, actual.currentContext(), null); + scalarQueue = null; + s.cancel(); + unsubscribe(); + } + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + s.request(Operators.unboundedOrPrefetch(maxConcurrency)); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + Publisher p; + + try { + p = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null Publisher"); + } + catch (Throwable e) { + Context ctx = actual.currentContext(); + Throwable e_ = Operators.onNextError(t, e, ctx, s); + Operators.onDiscard(t, ctx); + if (e_ != null) { + onError(e_); + } + else { + tryEmitScalar(null); + } + return; + } + + if (p instanceof Callable) { + R v; + try { + v = ((Callable) p).call(); + } + catch (Throwable e) { + Context ctx = actual.currentContext(); + //does the strategy apply? if so, short-circuit the delayError. In any case, don't cancel + Throwable e_ = Operators.onNextError(t, e, ctx); + if (e_ == null) { + tryEmitScalar(null); + } + else if (!delayError || !Exceptions.addThrowable(ERROR, this, e_)) { + //now if error mode strategy doesn't apply, let delayError play + onError(Operators.onOperatorError(s, e_, t, ctx)); + } + Operators.onDiscard(t, ctx); + return; + } + tryEmitScalar(v); + } + else { + FlatMapInner inner = new FlatMapInner<>(this, prefetch); + if (add(inner)) { + p.subscribe(inner); + } else { + Operators.onDiscard(t, actual.currentContext()); + } + } + + } + + Queue getOrCreateScalarQueue() { + Queue q = scalarQueue; + if (q == null) { + q = mainQueueSupplier.get(); + scalarQueue = q; + } + return q; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(null); + } + else { + Operators.onErrorDropped(t, actual.currentContext()); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + + done = true; + drain(null); + } + + void tryEmitScalar(@Nullable R v) { + if (v == null) { + if (maxConcurrency != Integer.MAX_VALUE) { + int p = produced + 1; + if (p == limit) { + produced = 0; + s.request(p); + } + else { + produced = p; + } + } + return; + } + + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + long r = requested; + + Queue q = scalarQueue; + if (r != 0L && (q == null || q.isEmpty())) { + actual.onNext(v); + + if (r != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + + if (maxConcurrency != Integer.MAX_VALUE) { + int p = produced + 1; + if (p == limit) { + produced = 0; + s.request(p); + } + else { + produced = p; + } + } + } + else { + if (q == null) { + q = getOrCreateScalarQueue(); + } + + if (!q.offer(v) && failOverflow(v, s)){ + done = true; + drainLoop(); + return; + } + } + if (WIP.decrementAndGet(this) == 0) { + if (cancelled) { + Operators.onDiscard(v, actual.currentContext()); + } + return; + } + + drainLoop(); + } + else { + Queue q; + + q = getOrCreateScalarQueue(); + + if (!q.offer(v) && failOverflow(v, s)) { + done = true; + } + drain(v); + } + } + + void tryEmit(FlatMapInner inner, R v) { + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + long r = requested; + + Queue q = inner.queue; + if (r != 0 && (q == null || q.isEmpty())) { + actual.onNext(v); + + if (r != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + + inner.request(1); + } + else { + if (q == null) { + q = getOrCreateInnerQueue(inner); + } + + if (!q.offer(v) && failOverflow(v, inner)){ + inner.done = true; + drainLoop(); + return; + } + } + if (WIP.decrementAndGet(this) == 0) { + if (cancelled) { + Operators.onDiscard(v, actual.currentContext()); + } + return; + } + + drainLoop(); + } + else { + Queue q = getOrCreateInnerQueue(inner); + + if (!q.offer(v) && failOverflow(v, inner)) { + inner.done = true; + } + drain(v); + } + } + + void drain(@Nullable R dataSignal) { + if (WIP.getAndIncrement(this) != 0) { + if (dataSignal != null && cancelled) { + Operators.onDiscard(dataSignal, actual.currentContext()); + } + return; + } + drainLoop(); + } + + void drainLoop() { + int missed = 1; + + final Subscriber a = actual; + + for (; ; ) { + + boolean d = done; + + FlatMapInner[] as = get(); + + int n = as.length; + + Queue sq = scalarQueue; + + boolean noSources = isEmpty(); + + if (checkTerminated(d, noSources && (sq == null || sq.isEmpty()), a, null)) { + return; + } + + boolean again = false; + + long r = requested; + long e = 0L; + long replenishMain = 0L; + + if (r != 0L && sq != null) { + + while (e != r) { + d = done; + + R v = sq.poll(); + + boolean empty = v == null; + + if (checkTerminated(d, false, a, v)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e != 0L) { + replenishMain += e; + if (r != Long.MAX_VALUE) { + r = REQUESTED.addAndGet(this, -e); + } + e = 0L; + again = true; + } + } + if (r != 0L && !noSources) { + + int j = lastIndex; + //Do not need to wrap j since lastIndex is always 0..= n) { +// j = 0; +// } + + for (int i = 0; i < n; i++) { + if (cancelled) { + Operators.onDiscardQueueWithClear(scalarQueue, actual.currentContext(), null); + scalarQueue = null; + s.cancel(); + unsubscribe(); + return; + } + + FlatMapInner inner = as[j]; + if (inner != null) { + d = inner.done; + Queue q = inner.queue; + if (d && q == null) { + remove(inner.index); + again = true; + replenishMain++; + } + else if (q != null) { + while (e != r) { + d = inner.done; + + R v; + + try { + v = q.poll(); + } + catch (Throwable ex) { + ex = Operators.onOperatorError(inner, ex, + actual.currentContext()); + if (!Exceptions.addThrowable(ERROR, this, ex)) { + Operators.onErrorDropped(ex, + actual.currentContext()); + } + v = null; + d = true; + } + + boolean empty = v == null; + + if (checkTerminated(d, false, a, v)) { + return; + } + + if (d && empty) { + remove(inner.index); + again = true; + replenishMain++; + break; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e == r) { + d = inner.done; + boolean empty = q.isEmpty(); + if (d && empty) { + remove(inner.index); + again = true; + replenishMain++; + } + } + + if (e != 0L) { + if (!inner.done) { + inner.request(e); + } + if (r != Long.MAX_VALUE) { + r = REQUESTED.addAndGet(this, -e); + if (r == 0L) { + break; // 0 .. n - 1 + } + } + e = 0L; + } + } + } + + if (r == 0L) { + break; + } + + if (++j == n) { + j = 0; + } + } + + lastIndex = j; + } + + if (r == 0L && !noSources) { + as = get(); + n = as.length; + + for (int i = 0; i < n; i++) { + if (cancelled) { + Operators.onDiscardQueueWithClear(scalarQueue, actual.currentContext(), null); + scalarQueue = null; + s.cancel(); + unsubscribe(); + return; + } + + FlatMapInner inner = as[i]; + if (inner == null) { + continue; + } + + d = inner.done; + Queue q = inner.queue; + boolean empty = (q == null || q.isEmpty()); + + // if we have a non-empty source then quit the cleanup + if (!empty) { + break; + } + + if (d && empty) { + remove(inner.index); + again = true; + replenishMain++; + } + } + } + + if (replenishMain != 0L && !done && !cancelled) { + s.request(replenishMain); + } + + if (again) { + continue; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, @Nullable R value) { + if (cancelled) { + Context ctx = actual.currentContext(); + Operators.onDiscard(value, ctx); + Operators.onDiscardQueueWithClear(scalarQueue, ctx, null); + scalarQueue = null; + s.cancel(); + unsubscribe(); + + return true; + } + + if (delayError) { + if (d && empty) { + Throwable e = error; + if (e != null && e != Exceptions.TERMINATED) { + e = Exceptions.terminate(ERROR, this); + a.onError(e); + } + else { + a.onComplete(); + } + + return true; + } + } + else { + if (d) { + Throwable e = error; + if (e != null && e != Exceptions.TERMINATED) { + e = Exceptions.terminate(ERROR, this); + Context ctx = actual.currentContext(); + Operators.onDiscard(value, ctx); + Operators.onDiscardQueueWithClear(scalarQueue, ctx, null); + scalarQueue = null; + s.cancel(); + unsubscribe(); + + a.onError(e); + return true; + } + else if (empty) { + a.onComplete(); + return true; + } + } + } + + return false; + } + + void innerError(FlatMapInner inner, Throwable e) { + e = Operators.onNextInnerError(e, currentContext(), s); + if (e != null) { + if (Exceptions.addThrowable(ERROR, this, e)) { + if (!delayError) { + done = true; + } + inner.done = true; + drain(null); + } + else { + inner.done = true; + Operators.onErrorDropped(e, actual.currentContext()); + } + } + else { + inner.done = true; + drain(null); + } + } + + boolean failOverflow(R v, Subscription toCancel){ + Throwable e = Operators.onOperatorError(toCancel, + Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), + v, actual.currentContext()); + + Operators.onDiscard(v, actual.currentContext()); + + if (!Exceptions.addThrowable(ERROR, this, e)) { + Operators.onErrorDropped(e, actual.currentContext()); + return false; + } + return true; + } + + void innerComplete(FlatMapInner inner) { + if (WIP.getAndIncrement(this) != 0) { + return; + } + drainLoop(); + } + + Queue getOrCreateInnerQueue(FlatMapInner inner) { + Queue q = inner.queue; + if (q == null) { + q = innerQueueSupplier.get(); + inner.queue = q; + } + return q; + } + + } + + static final class FlatMapInner + implements InnerConsumer, Subscription { + + final FlatMapMain parent; + + final int prefetch; + + final int limit; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(FlatMapInner.class, + Subscription.class, + "s"); + + long produced; + + volatile Queue queue; + + volatile boolean done; + + /** + * Represents the optimization mode of this inner subscriber. + */ + int sourceMode; + + int index; + + FlatMapInner(FlatMapMain parent, int prefetch) { + this.parent = parent; + this.prefetch = prefetch; +// this.limit = prefetch >> 2; + this.limit = Operators.unboundedOrLimit(prefetch); + } + + @Override + public void onSubscribe(Subscription s) { + 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 | Fuseable.THREAD_BARRIER); + if (m == Fuseable.SYNC) { + sourceMode = Fuseable.SYNC; + queue = f; + done = true; + parent.drain(null); + return; + } + if (m == Fuseable.ASYNC) { + sourceMode = Fuseable.ASYNC; + queue = f; + } + // NONE is just fall-through as the queue will be created on demand + } + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(R t) { + if (sourceMode == Fuseable.ASYNC) { + parent.drain(t); + } + else { + if (done) { + Operators.onNextDropped(t, parent.currentContext()); + return; + } + + if (s == Operators.cancelledSubscription()) { + Operators.onDiscard(t, parent.currentContext()); + return; + } + + parent.tryEmit(this, t); + } + } + + @Override + public void onError(Throwable t) { + parent.innerError(this, t); //we want to delay the marking as done + } + + @Override + public void onComplete() { + // onComplete is practically idempotent so there is no risk due to subscription-race in async mode + done = true; + parent.innerComplete(this); + } + + @Override + public void request(long n) { + if (sourceMode == Fuseable.SYNC) { + return; + } + long p = produced + n; + if (p >= limit) { + produced = 0L; + s.request(p); + } + else { + produced = p; + } + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + public void cancel() { + Operators.terminate(S, this); + Operators.onDiscardQueueWithClear(queue, parent.currentContext(), null); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.TERMINATED) return done && (queue == null || queue.isEmpty()); + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.BUFFERED) return queue == null ? 0 : queue.size(); + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } +} + +abstract class FlatMapTracker { + + volatile T[] array = empty(); + + int[] free = FREE_EMPTY; + + long producerIndex; + long consumerIndex; + + volatile int size; + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater SIZE = + AtomicIntegerFieldUpdater.newUpdater(FlatMapTracker.class, "size"); + + static final int[] FREE_EMPTY = new int[0]; + + abstract T[] empty(); + + abstract T[] terminated(); + + abstract T[] newArray(int size); + + abstract void unsubscribeEntry(T entry); + + abstract void setIndex(T entry, int index); + + final void unsubscribe() { + T[] a; + T[] t = terminated(); + synchronized (this) { + a = array; + if (a == t) { + return; + } + SIZE.lazySet(this, 0); + free = null; + array = t; + } + for (T e : a) { + if (e != null) { + unsubscribeEntry(e); + } + } + } + + final T[] get() { + return array; + } + + final boolean add(T entry) { + T[] a = array; + if (a == terminated()) { + return false; + } + synchronized (this) { + a = array; + if (a == terminated()) { + return false; + } + + int idx = pollFree(); + if (idx < 0) { + int n = a.length; + T[] b = n != 0 ? newArray(n << 1) : newArray(4); + System.arraycopy(a, 0, b, 0, n); + + array = b; + a = b; + + int m = b.length; + int[] u = new int[m]; + for (int i = n + 1; i < m; i++) { + u[i] = i; + } + free = u; + consumerIndex = n + 1; + producerIndex = m; + + idx = n; + } + setIndex(entry, idx); + SIZE.lazySet(this, size); // make sure entry is released + a[idx] = entry; + SIZE.lazySet(this, size + 1); + } + return true; + } + + final void remove(int index) { + synchronized (this) { + T[] a = array; + if (a != terminated()) { + a[index] = null; + offerFree(index); + SIZE.lazySet(this, size - 1); + } + } + } + + int pollFree() { + int[] a = free; + int m = a.length - 1; + long ci = consumerIndex; + if (producerIndex == ci) { + return -1; + } + int offset = (int) ci & m; + consumerIndex = ci + 1; + return a[offset]; + } + + void offerFree(int index) { + int[] a = free; + int m = a.length - 1; + long pi = producerIndex; + int offset = (int) pi & m; + a[offset] = index; + producerIndex = pi + 1; + } + + final boolean isEmpty() { + return size == 0; + } +} + Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFlattenIterable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFlattenIterable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFlattenIterable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,764 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.Objects; +import java.util.Queue; +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.Function; +import java.util.function.Supplier; + +import org.reactivestreams.Subscriber; +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; + +/** + * Concatenates values from Iterable sequences generated via a mapper function. + * + * @param the input value type + * @param the value type of the iterables and the result type + * + * @see Reactive-Streams-Commons + */ +final class FluxFlattenIterable extends InternalFluxOperator implements Fuseable { + + final Function> mapper; + + final int prefetch; + + final Supplier> queueSupplier; + + FluxFlattenIterable(Flux source, + Function> mapper, + int prefetch, + Supplier> queueSupplier) { + super(source); + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.mapper = Objects.requireNonNull(mapper, "mapper"); + this.prefetch = prefetch; + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Exception { + + if (source instanceof Callable) { + T v = ((Callable) source).call(); + + if (v == null) { + Operators.complete(actual); + return null; + } + + Iterator it; + boolean knownToBeFinite; + try { + Iterable iter = mapper.apply(v); + it = iter.iterator(); + knownToBeFinite = FluxIterable.checkFinite(iter); + } + catch (Throwable ex) { + Context ctx = actual.currentContext(); + Throwable e_ = Operators.onNextError(v, ex, ctx); + Operators.onDiscard(v, ctx); + if (e_ != null) { + Operators.error(actual, e_); + } + else { + Operators.complete(actual); + } + return null; + } + + // TODO return subscriber (tail-call optimization)? + FluxIterable.subscribe(actual, it, knownToBeFinite); + return null; + } + return new FlattenIterableSubscriber<>(actual, + mapper, + prefetch, + queueSupplier); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class FlattenIterableSubscriber + implements InnerOperator, QueueSubscription { + + final CoreSubscriber actual; + + final Function> mapper; + + final int prefetch; + + final int limit; + + final Supplier> queueSupplier; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(FlattenIterableSubscriber.class, + "wip"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(FlattenIterableSubscriber.class, + "requested"); + + Subscription s; + + Queue queue; + + volatile boolean done; + + volatile boolean cancelled; + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + ERROR = + AtomicReferenceFieldUpdater.newUpdater(FlattenIterableSubscriber.class, + Throwable.class, + "error"); + + @Nullable + Iterator current; + boolean currentKnownToBeFinite; + + int consumed; + + int fusionMode; + + FlattenIterableSubscriber(CoreSubscriber actual, + Function> mapper, + int prefetch, + Supplier> queueSupplier) { + this.actual = actual; + this.mapper = mapper; + this.prefetch = prefetch; + this.queueSupplier = queueSupplier; + this.limit = Operators.unboundedOrLimit(prefetch); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.ERROR) return error; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") QueueSubscription qs = + (QueueSubscription) s; + + int m = qs.requestFusion(Fuseable.ANY); + + if (m == Fuseable.SYNC) { + fusionMode = m; + this.queue = qs; + done = true; + + actual.onSubscribe(this); + + return; + } + else if (m == Fuseable.ASYNC) { + fusionMode = m; + this.queue = qs; + + actual.onSubscribe(this); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + return; + } + } + + queue = queueSupplier.get(); + + actual.onSubscribe(this); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if (fusionMode != Fuseable.ASYNC) { + if (!queue.offer(t)) { + Context ctx = actual.currentContext(); + onError(Operators.onOperatorError(s,Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), + ctx)); + Operators.onDiscard(t, ctx); + return; + } + } + drain(t); + } + + @Override + public void onError(Throwable t) { + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(null); + } + else { + Operators.onErrorDropped(t, actual.currentContext()); + } + } + + @Override + public void onComplete() { + done = true; + drain(null); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(null); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + s.cancel(); + + if (WIP.getAndIncrement(this) == 0) { + Context context = actual.currentContext(); + Operators.onDiscardQueueWithClear(queue, context, null); + Operators.onDiscardMultiple(current, currentKnownToBeFinite, context); + } + } + } + + //should be kept small and final to favor inlining + final void resetCurrent() { + current = null; + currentKnownToBeFinite = false; + } + + void drainAsync() { + final Subscriber a = actual; + final Queue q = queue; + + int missed = 1; + Iterator it = current; + boolean itFinite = currentKnownToBeFinite; + + for (; ; ) { + + if (it == null) { + + if (cancelled) { + Operators.onDiscardQueueWithClear(q, actual.currentContext(), null); + return; + } + + Throwable ex = error; + if (ex != null) { + ex = Exceptions.terminate(ERROR, this); + resetCurrent(); + Operators.onDiscardQueueWithClear(q, actual.currentContext(), null); + a.onError(ex); + return; + } + + boolean d = done; + + T t; + + try { + t = q.poll(); + } catch (Throwable pollEx) { + resetCurrent(); + Operators.onDiscardQueueWithClear(q, actual.currentContext(), null); + a.onError(pollEx); + return; + } + + boolean empty = t == null; + + if (d && empty) { + a.onComplete(); + return; + } + + if (!empty) { + Iterable iterable; + + boolean b; + + try { + iterable = mapper.apply(t); + it = iterable.iterator(); + itFinite = FluxIterable.checkFinite(iterable); + + b = it.hasNext(); + } + catch (Throwable exc) { + it = null; + itFinite = false; //reset explicitly + Context ctx = actual.currentContext(); + Throwable e_ = Operators.onNextError(t, exc, ctx, s); + Operators.onDiscard(t, ctx); + if (e_ != null) { + onError(e_); + } + continue; + } + + if (!b) { + it = null; + itFinite = false; //reset explicitly + int c = consumed + 1; + if (c == limit) { + consumed = 0; + s.request(c); + } + else { + consumed = c; + } + continue; + } + } + } + + if (it != null) { + long r = requested; + long e = 0L; + + while (e != r) { + if (cancelled) { + resetCurrent(); + final Context context = actual.currentContext(); + Operators.onDiscardQueueWithClear(q, context, null); + Operators.onDiscardMultiple(it, itFinite, context); + return; + } + + Throwable ex = error; + if (ex != null) { + ex = Exceptions.terminate(ERROR, this); + resetCurrent(); + final Context context = actual.currentContext(); + Operators.onDiscardQueueWithClear(q, context, null); + Operators.onDiscardMultiple(it, itFinite, context); + a.onError(ex); + return; + } + + R v; + + try { + v = Objects.requireNonNull(it.next(), + "iterator returned null"); + } + catch (Throwable exc) { + onError(Operators.onOperatorError(s, exc, + actual.currentContext())); + continue; + } + + a.onNext(v); + + if (cancelled) { + resetCurrent(); + final Context context = actual.currentContext(); + Operators.onDiscardQueueWithClear(q, context, null); + Operators.onDiscardMultiple(it, itFinite, context); + return; + } + + e++; + + boolean b; + + try { + b = it.hasNext(); + } + catch (Throwable exc) { + onError(Operators.onOperatorError(s, exc, + actual.currentContext())); + continue; + } + + if (!b) { + int c = consumed + 1; + if (c == limit) { + consumed = 0; + s.request(c); + } + else { + consumed = c; + } + it = null; + itFinite = false; + resetCurrent(); + break; + } + } + + if (e == r) { + if (cancelled) { + resetCurrent(); + final Context context = actual.currentContext(); + Operators.onDiscardQueueWithClear(q, context, null); + Operators.onDiscardMultiple(it, itFinite, context); + return; + } + + Throwable ex = error; + if (ex != null) { + ex = Exceptions.terminate(ERROR, this); + resetCurrent(); + final Context context = actual.currentContext(); + Operators.onDiscardQueueWithClear(q, context, null); + Operators.onDiscardMultiple(it, itFinite, context); + a.onError(ex); + return; + } + + boolean d = done; + boolean empty = q.isEmpty() && it == null; + + if (d && empty) { + resetCurrent(); + a.onComplete(); + return; + } + } + + if (e != 0L) { + if (r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + } + + if (it == null) { + continue; + } + } + + current = it; + currentKnownToBeFinite = itFinite; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drainSync() { + final Subscriber a = actual; + + int missed = 1; + Iterator it = current; + boolean itFinite = currentKnownToBeFinite; + + for (; ; ) { + if (it == null) { + + if (cancelled) { + Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); + return; + } + + boolean d = done; + + T t; + Queue q = queue; + + try { + t = q.poll(); + } catch (Throwable pollEx) { + resetCurrent(); + Operators.onDiscardQueueWithClear(q, actual.currentContext(), null); + a.onError(pollEx); + return; + } + + boolean empty = t == null; + + if (d && empty) { + a.onComplete(); + return; + } + + if (!empty) { + Iterable iterable; + + boolean b; + + try { + iterable = mapper.apply(t); + it = iterable.iterator(); + itFinite = FluxIterable.checkFinite(iterable); + + b = it.hasNext(); + } + catch (Throwable exc) { + resetCurrent(); + Context ctx = actual.currentContext(); + Throwable e_ = Operators.onNextError(t, exc, ctx, s); + //note: if there is an exception, we can consider the iterator done, + // so no attempt is made to discard remainder here + Operators.onDiscard(t, ctx); + if (e_ != null) { + a.onError(e_); + return; + } + continue; + } + + if (!b) { + it = null; + itFinite = false; + continue; + } + } + } + + if (it != null) { + long r = requested; + long e = 0L; + + while (e != r) { + if (cancelled) { + resetCurrent(); + final Context context = actual.currentContext(); + Operators.onDiscardQueueWithClear(queue, context, null); + Operators.onDiscardMultiple(it, itFinite, context); + return; + } + + R v; + + try { + v = Objects.requireNonNull(it.next(), "iterator returned null"); + } + catch (Throwable exc) { + resetCurrent(); + a.onError(Operators.onOperatorError(s, exc, actual.currentContext())); + return; + } + + a.onNext(v); + + if (cancelled) { + resetCurrent(); + final Context context = actual.currentContext(); + Operators.onDiscardQueueWithClear(queue, context, null); + Operators.onDiscardMultiple(it, itFinite, context); + return; + } + + e++; + + boolean b; + + try { + b = it.hasNext(); + } + catch (Throwable exc) { + resetCurrent(); + a.onError(Operators.onOperatorError(s, exc, actual.currentContext())); + return; + } + + if (!b) { + it = null; + itFinite = false; + resetCurrent(); + break; + } + } + + if (e == r) { + if (cancelled) { + resetCurrent(); + final Context context = actual.currentContext(); + Operators.onDiscardQueueWithClear(queue, context, null); + Operators.onDiscardMultiple(it, itFinite, context); + return; + } + + boolean d = done; + boolean empty = queue.isEmpty() && it == null; + + if (d && empty) { + resetCurrent(); + a.onComplete(); + return; + } + } + + if (e != 0L) { + if (r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + } + + if (it == null) { + continue; + } + } + + current = it; + currentKnownToBeFinite = itFinite; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drain(@Nullable T dataSignal) { + if (WIP.getAndIncrement(this) != 0) { + if (dataSignal != null && cancelled) { + Operators.onDiscard(dataSignal, actual.currentContext()); + } + return; + } + + if (fusionMode == SYNC) { + drainSync(); + } + else { + drainAsync(); + } + } + + @Override + public void clear() { + final Context context = actual.currentContext(); + Operators.onDiscardMultiple(current, currentKnownToBeFinite, context); + resetCurrent(); + Operators.onDiscardQueueWithClear(queue, context, null); + } + + @Override + public boolean isEmpty() { + Iterator it = current; + if (it != null) { + return !it.hasNext(); + } + return queue.isEmpty(); // estimate + } + + @Override + @Nullable + public R poll() { + Iterator it = current; + boolean itFinite; + for (; ; ) { + if (it == null) { + T v = queue.poll(); + if (v == null) { + return null; + } + + Iterable iterable; + try { + iterable = mapper.apply(v); + it = iterable.iterator(); + itFinite = FluxIterable.checkFinite(iterable); + } + catch (Throwable error) { + Operators.onDiscard(v, actual.currentContext()); + throw error; + } + + if (!it.hasNext()) { + continue; + } + current = it; + currentKnownToBeFinite = itFinite; + } + else if (!it.hasNext()) { + it = null; + continue; + } + + R r = Objects.requireNonNull(it.next(), "iterator returned null"); + + if (!it.hasNext()) { + resetCurrent(); + } + + return r; + } + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & SYNC) != 0 && fusionMode == SYNC) { + return SYNC; + } + return NONE; + } + + @Override + public int size() { + return queue.size(); // estimate + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxFromMonoOperator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxFromMonoOperator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxFromMonoOperator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * A decorating {@link Flux} {@link Publisher} that exposes {@link Flux} API over an + * arbitrary {@link Publisher}. Useful to create operators which return a {@link Flux}. + * + * @param delegate {@link Publisher} type + * @param produced type + */ +abstract class FluxFromMonoOperator extends Flux implements Scannable, + OptimizableOperator { + + protected final Mono source; + + @Nullable + final OptimizableOperator optimizableOperator; + + /** + * Build a {@link FluxFromMonoOperator} wrapper around the passed parent {@link Publisher} + * + * @param source the {@link Publisher} to decorate + */ + @SuppressWarnings("unchecked") + protected FluxFromMonoOperator(Mono source) { + this.source = Objects.requireNonNull(source); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + return null; + } + + + @Override + @SuppressWarnings("unchecked") + public final void subscribe(CoreSubscriber subscriber) { + OptimizableOperator operator = this; + try { + while (true) { + subscriber = operator.subscribeOrReturn(subscriber); + if (subscriber == null) { + // null means "I will subscribe myself", returning... + return; + } + OptimizableOperator newSource = operator.nextOptimizableSource(); + if (newSource == null) { + operator.source().subscribe(subscriber); + return; + } + operator = newSource; + } + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(subscriber, e); + return; + } + } + + @Override + @Nullable + public abstract CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable; + + @Override + public final CorePublisher source() { + return source; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxGenerate.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxGenerate.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxGenerate.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Generate signals one-by-one via a function callback. + *

+ *

+ * The {@code stateSupplier} may return {@code null} but your {@code stateConsumer} should be prepared to + * handle it. + * + * @param the value type emitted + * @param the custom state per subscriber + * @see https://github.com/reactor/reactive-streams-commons + */ +final class FluxGenerate +extends Flux implements Fuseable, SourceProducer { + + + static final Callable EMPTY_CALLABLE = () -> null; + + final Callable stateSupplier; + + final BiFunction, S> generator; + + final Consumer stateConsumer; + + @SuppressWarnings("unchecked") + FluxGenerate(Consumer> generator) { + this(EMPTY_CALLABLE, (state,sink) -> { + generator.accept(sink); + return null; + }); + } + + FluxGenerate(Callable stateSupplier, BiFunction, S> generator) { + this(stateSupplier, generator, s -> { + }); + } + + FluxGenerate(Callable stateSupplier, BiFunction, S> generator, + Consumer stateConsumer) { + this.stateSupplier = Objects.requireNonNull(stateSupplier, "stateSupplier"); + this.generator = Objects.requireNonNull(generator, "generator"); + this.stateConsumer = Objects.requireNonNull(stateConsumer, "stateConsumer"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + S state; + + try { + state = stateSupplier.call(); + } catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + actual.onSubscribe(new GenerateSubscription<>(actual, state, generator, stateConsumer)); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class GenerateSubscription + implements QueueSubscription, InnerProducer, SynchronousSink { + + final CoreSubscriber actual; + + final BiFunction, S> generator; + + final Consumer stateConsumer; + + volatile boolean cancelled; + + S state; + + boolean terminate; + + boolean hasValue; + + boolean outputFused; + + T generatedValue; + + Throwable generatedError; + + volatile long requested; + + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(GenerateSubscription.class, "requested"); + + GenerateSubscription(CoreSubscriber actual, S state, + BiFunction, S> generator, Consumer stateConsumer) { + this.actual = actual; + this.state = state; + this.generator = generator; + this.stateConsumer = stateConsumer; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return terminate; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.ERROR) return generatedError; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void next(T t) { + if (terminate) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + if (hasValue) { + error(new IllegalStateException("More than one call to onNext")); + return; + } + //noinspection ConstantConditions + if (t == null) { + error(new NullPointerException("The generator produced a null value")); + return; + } + hasValue = true; + if (outputFused) { + generatedValue = t; + } else { + actual.onNext(t); + } + } + + @Override + public void error(Throwable e) { + if (terminate) { + return; + } + terminate = true; + if (outputFused) { + generatedError = e; + } else { + actual.onError(e); + } + } + + @Override + public void complete() { + if (terminate) { + return; + } + terminate = true; + if (!outputFused) { + actual.onComplete(); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (Operators.addCap(REQUESTED, this, n) == 0) { + if (n == Long.MAX_VALUE) { + fastPath(); + } else { + slowPath(n); + } + } + } + } + + void fastPath() { + S s = state; + + final BiFunction, S> g = generator; + + for (; ; ) { + + if (cancelled) { + cleanup(s); + return; + } + + try { + s = g.apply(s, this); + } catch (Throwable e) { + cleanup(s); + + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + return; + } + if (terminate || cancelled) { + cleanup(s); + return; + } + if (!hasValue) { + cleanup(s); + + actual.onError(new IllegalStateException("The generator didn't call any of the " + + "SynchronousSink method")); + return; + } + + hasValue = false; + } + } + + void slowPath(long n) { + S s = state; + + long e = 0L; + + final BiFunction, S> g = generator; + + for (; ; ) { + while (e != n) { + + if (cancelled) { + cleanup(s); + return; + } + + try { + s = g.apply(s, this); + } catch (Throwable ex) { + cleanup(s); + + actual.onError(ex); + return; + } + if (terminate || cancelled) { + cleanup(s); + return; + } + if (!hasValue) { + cleanup(s); + + actual.onError(new IllegalStateException("The generator didn't call any of the " + + "SynchronousSink method")); + return; + } + + e++; + hasValue = false; + } + + n = requested; + + if (n == e) { + state = s; + n = REQUESTED.addAndGet(this, -e); + if (n == 0L) { + return; + } + } + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + if (REQUESTED.getAndIncrement(this) == 0) { + cleanup(state); + } + } + } + + void cleanup(S s) { + try { + state = null; + + stateConsumer.accept(s); + } catch (Throwable e) { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & Fuseable.SYNC) != 0 && (requestedMode & Fuseable.THREAD_BARRIER) == 0) { + outputFused = true; + return Fuseable.SYNC; + } + return Fuseable.NONE; + } + + @Override + @Nullable + public T poll() { + S s = state; + + if (terminate) { + cleanup(s); + + Throwable e = generatedError; + if (e != null) { + + generatedError = null; + throw Exceptions.propagate(e); + } + + return null; + } + + + try { + s = generator.apply(s, this); + } catch (final Throwable ex) { + cleanup(s); + throw ex; + } + + if (!hasValue) { + cleanup(s); + + if (!terminate) { + throw new IllegalStateException("The generator didn't call any of the SynchronousSink method"); + } + + Throwable e = generatedError; + if (e != null) { + + generatedError = null; + throw Exceptions.propagate(e); + } + + return null; + } + + T v = generatedValue; + generatedValue = null; + hasValue = false; + + state = s; + return v; + } + + @Override + public boolean isEmpty() { + return terminate; + } + + @Override + public int size() { + return isEmpty() ? 0 : -1; + } + + @Override + public void clear() { + generatedError = null; + generatedValue = null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxGroupBy.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxGroupBy.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxGroupBy.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,831 @@ +/* + * Copyright (c) 2016-2021 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.Map; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.reactivestreams.Subscriber; +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; + +/** + * Groups upstream items into their own Publisher sequence based on a key selector. + * + * @param the source value type + * @param the key value type + * @param the group item value type + * + * @see Reactive-Streams-Commons + */ +final class FluxGroupBy extends InternalFluxOperator> + implements Fuseable { + + final Function keySelector; + + final Function valueSelector; + + final Supplier> groupQueueSupplier; + + final Supplier>> mainQueueSupplier; + + final int prefetch; + + FluxGroupBy(Flux source, + Function keySelector, + Function valueSelector, + Supplier>> mainQueueSupplier, + Supplier> groupQueueSupplier, + int prefetch) { + super(source); + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.keySelector = Objects.requireNonNull(keySelector, "keySelector"); + this.valueSelector = Objects.requireNonNull(valueSelector, "valueSelector"); + this.mainQueueSupplier = + Objects.requireNonNull(mainQueueSupplier, "mainQueueSupplier"); + this.groupQueueSupplier = + Objects.requireNonNull(groupQueueSupplier, "groupQueueSupplier"); + this.prefetch = prefetch; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new GroupByMain<>(actual, + mainQueueSupplier.get(), + groupQueueSupplier, + prefetch, + keySelector, valueSelector); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class GroupByMain + implements QueueSubscription>, + InnerOperator> { + + final Function keySelector; + final Function valueSelector; + final Queue> queue; + final Supplier> groupQueueSupplier; + final int prefetch; + final Map> groupMap; + final CoreSubscriber> actual; + + volatile int wip; + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(GroupByMain.class, "wip"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(GroupByMain.class, "requested"); + + volatile boolean done; + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(GroupByMain.class, + Throwable.class, + "error"); + + volatile int cancelled; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater CANCELLED = + AtomicIntegerFieldUpdater.newUpdater(GroupByMain.class, "cancelled"); + + volatile int groupCount; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater GROUP_COUNT = + AtomicIntegerFieldUpdater.newUpdater(GroupByMain.class, "groupCount"); + + Subscription s; + + volatile boolean enableAsyncFusion; + + GroupByMain(CoreSubscriber> actual, + Queue> queue, + Supplier> groupQueueSupplier, + int prefetch, + Function keySelector, + Function valueSelector) { + this.actual = actual; + this.queue = queue; + this.groupQueueSupplier = groupQueueSupplier; + this.prefetch = prefetch; + this.groupMap = new ConcurrentHashMap<>(); + this.keySelector = keySelector; + this.valueSelector = valueSelector; + GROUP_COUNT.lazySet(this, 1); + } + + @Override + public final CoreSubscriber> actual() { + return actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if(done){ + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + K key; + V value; + + try { + key = Objects.requireNonNull(keySelector.apply(t), "The keySelector returned a null value"); + value = Objects.requireNonNull(valueSelector.apply(t), "The valueSelector returned a null value"); + } + catch (Throwable ex) { + onError(Operators.onOperatorError(s, ex, t, actual.currentContext())); + return; + } + + UnicastGroupedFlux g = groupMap.get(key); + + if (g == null) { + // if the main is cancelled, don't create new groups + if (cancelled == 0) { + Queue q = groupQueueSupplier.get(); + + GROUP_COUNT.getAndIncrement(this); + g = new UnicastGroupedFlux<>(key, q, this, prefetch); + g.onNext(value); + groupMap.put(key, g); + + queue.offer(g); + drain(); + } + } + else { + g.onNext(value); + } + } + + @Override + public void onError(Throwable t) { + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(); + } + else { + Operators.onErrorDropped(t, actual.currentContext()); + } + } + + @Override + public void onComplete() { + if(done){ + return; + } + for (UnicastGroupedFlux g : groupMap.values()) { + g.onComplete(); + } + groupMap.clear(); + done = true; + drain(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.BUFFERED) return queue.size(); + if (key == Attr.CANCELLED) return cancelled == 1; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return groupMap.values().stream(); + } + + void signalAsyncError() { + Throwable e = Exceptions.terminate(ERROR, this); //TODO investigate if e == null + if (e == null) { + e = new IllegalStateException("FluxGroupBy.signalAsyncError called without error set"); + } + groupCount = 0; + for (UnicastGroupedFlux g : groupMap.values()) { + g.onError(e); + } + actual.onError(e); + groupMap.clear(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (CANCELLED.compareAndSet(this, 0, 1)) { + if (GROUP_COUNT.decrementAndGet(this) == 0) { + s.cancel(); + } + else if (!enableAsyncFusion) { + if (WIP.getAndIncrement(this) == 0) { + // remove queued up but unobservable groups from the mapping + GroupedFlux g; + while ((g = queue.poll()) != null) { + ((UnicastGroupedFlux) g).cancel(); + } + + if (WIP.decrementAndGet(this) == 0) { + return; + } + + drainLoop(); + } + } + } + } + + void groupTerminated(K key) { + if (groupCount == 0) { + return; + } + groupMap.remove(key); + int groupRemaining = GROUP_COUNT.decrementAndGet(this); + if (groupRemaining == 0) { + s.cancel(); + } + else if (groupRemaining == 1) { + //there is an "extra" group count for the global cancellation, so the operator as a whole is still active + //we want at least one more group + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + if (enableAsyncFusion) { + drainFused(); + } + else { + drainLoop(); + } + } + + void drainFused() { + int missed = 1; + + final Subscriber> a = actual; + final Queue> q = queue; + + for (; ; ) { + + if (cancelled != 0) { + q.clear(); + return; + } + + boolean d = done; + + a.onNext(null); + + if (d) { + Throwable ex = error; + if (ex != null) { + signalAsyncError(); + } + else { + a.onComplete(); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drainLoop() { + + int missed = 1; + + Subscriber> a = actual; + Queue> q = queue; + + for (; ; ) { + + long r = requested; + long e = 0L; + + while (e != r) { + boolean d = done; + GroupedFlux v = q.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e == r) { + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + } + + if (e != 0L) { + s.request(e); + + if (r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, + boolean empty, + Subscriber a, + Queue> q) { + if (d) { + Throwable e = error; + if (e != null && e != Exceptions.TERMINATED) { + q.clear(); + signalAsyncError(); + return true; + } + else if (empty) { + a.onComplete(); + return true; + } + } + + return false; + } + + @Override + @Nullable + public GroupedFlux poll() { + return queue.poll(); + } + + @Override + public int size() { + return queue.size(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & Fuseable.ASYNC) != 0) { + enableAsyncFusion = true; + return Fuseable.ASYNC; + } + return Fuseable.NONE; + } + } + + static final class UnicastGroupedFlux extends GroupedFlux + implements Fuseable, QueueSubscription, InnerProducer { + + final K key; + + final int limit; + + final Context context; + + @Override + public K key() { + return key; + } + + final Queue queue; + + volatile GroupByMain parent; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater PARENT = + AtomicReferenceFieldUpdater.newUpdater(UnicastGroupedFlux.class, + GroupByMain.class, + "parent"); + + volatile boolean done; + Throwable error; + + volatile CoreSubscriber actual; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ACTUAL = + AtomicReferenceFieldUpdater.newUpdater(UnicastGroupedFlux.class, + CoreSubscriber.class, + "actual"); + + volatile boolean cancelled; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(UnicastGroupedFlux.class, "once"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(UnicastGroupedFlux.class, "wip"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(UnicastGroupedFlux.class, "requested"); + + volatile boolean outputFused; + + int produced; + boolean isFirstRequest = true; + + UnicastGroupedFlux(K key, + Queue queue, + GroupByMain parent, + int prefetch) { + this.key = key; + this.queue = queue; + this.context = parent.currentContext(); + this.parent = parent; + this.limit = Operators.unboundedOrLimit(prefetch); + } + + void doTerminate() { + GroupByMain r = parent; + if (r != null && PARENT.compareAndSet(this, r, null)) { + r.groupTerminated(key); + } + } + + void drainRegular(Subscriber a) { + int missed = 1; + + final Queue q = queue; + + for (; ; ) { + + long r = requested; + long e = 0L; + + while (r != e) { + boolean d = done; + + V t = q.poll(); + boolean empty = t == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + e++; + } + + if (r == e) { + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + } + + if (e != 0) { + GroupByMain main = parent; + if (main != null) { + if (this.isFirstRequest) { + this.isFirstRequest = false; + long toRequest = e - 1; + + if (toRequest > 0) { + main.s.request(toRequest); + } + } else { + main.s.request(e); + } + } + if (r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drainFused(Subscriber a) { + int missed = 1; + + final Queue q = queue; + + for (; ; ) { + + if (cancelled) { + q.clear(); + actual = null; + return; + } + + boolean d = done; + + a.onNext(null); + + if (d) { + actual = null; + + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drain() { + Subscriber a = actual; + if (a != null) { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + if (outputFused) { + drainFused(a); + } + else { + drainRegular(a); + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue q) { + if (cancelled) { + q.clear(); + actual = null; + return true; + } + if (d && empty) { + Throwable e = error; + actual = null; + if (e != null) { + a.onError(e); + } + else { + a.onComplete(); + } + return true; + } + + return false; + } + + public void onNext(V t) { + Subscriber a = actual; + + if (!queue.offer(t)) { + onError(Operators.onOperatorError(this, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t, + actual.currentContext())); + return; + } + if (outputFused) { + if (a != null) { + a.onNext(null); // in op-fusion, onNext(null) is the indicator of more data + } + } + else { + drain(); + } + } + + public void onError(Throwable t) { + error = t; + done = true; + + doTerminate(); + + drain(); + } + + public void onComplete() { + done = true; + + doTerminate(); + + drain(); + } + + @Override + public void subscribe(CoreSubscriber actual) { + if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { + actual.onSubscribe(this); + ACTUAL.lazySet(this, actual); + drain(); + } + else { + actual.onError(new IllegalStateException( + "GroupedFlux allows only one Subscriber")); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + + doTerminate(); + + if (!outputFused) { + if (WIP.getAndIncrement(this) == 0) { + queue.clear(); + } + } + } + + @Override + @Nullable + public V poll() { + V v = queue.poll(); + if (v != null) { + produced++; + } + else { + tryReplenish(); + } + return v; + } + + void tryReplenish() { + int p = produced; + if (p != 0) { + produced = 0; + GroupByMain main = parent; + if (main != null) { + if (this.isFirstRequest) { + this.isFirstRequest = false; + p--; + + if (p > 0) { + main.s.request(p); + } + } else { + main.s.request(p); + } + } + } + } + + @Override + public int size() { + return queue.size(); + } + + @Override + public boolean isEmpty() { + if (queue.isEmpty()) { + tryReplenish(); + return true; + } + return false; + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & Fuseable.ASYNC) != 0) { + outputFused = true; + return Fuseable.ASYNC; + } + return Fuseable.NONE; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.ERROR) return error; + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxGroupJoin.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxGroupJoin.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxGroupJoin.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2016-2021 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.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +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.BiPredicate; +import java.util.function.Function; +import java.util.function.Supplier; +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.Disposables; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * A Publisher that correlates two Publishers when they overlap in time and groups the + * results. + *

+ * There are no guarantees in what order the items get combined when multiple items from + * one or both source Publishers overlap. + * + * @param the left Publisher to correlate items from the source Publisher with + * @param the other Publisher to correlate items from the source Publisher with + * @param type that a function returns via a Publisher whose emissions indicate + * the duration of the values of the source Publisher + * @param type that a function that returns via a Publisher whose emissions + * indicate the duration of the values of the {@code right} Publisher + * @param type that a function that takes an item emitted by each Publisher and + * returns the value to be emitted by the resulting Publisher + * @see https://github.com/reactor/reactive-streams-commons + * @since 3.0 + */ +final class FluxGroupJoin + extends InternalFluxOperator { + + final Publisher other; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction, ? extends R> resultSelector; + + final Supplier> processorQueueSupplier; + + FluxGroupJoin(Flux source, + Publisher other, + Function> leftEnd, + Function> rightEnd, + BiFunction, ? extends R> resultSelector, + Supplier> queueSupplier, + Supplier> processorQueueSupplier) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + this.leftEnd = Objects.requireNonNull(leftEnd, "leftEnd"); + this.rightEnd = Objects.requireNonNull(rightEnd, "rightEnd"); + this.processorQueueSupplier = Objects.requireNonNull(processorQueueSupplier, "processorQueueSupplier"); + this.resultSelector = Objects.requireNonNull(resultSelector, "resultSelector"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + GroupJoinSubscription parent = + new GroupJoinSubscription<>(actual, + leftEnd, + rightEnd, + resultSelector, + processorQueueSupplier); + + actual.onSubscribe(parent); + + LeftRightSubscriber left = new LeftRightSubscriber(parent, true); + parent.cancellations.add(left); + LeftRightSubscriber right = new LeftRightSubscriber(parent, false); + parent.cancellations.add(right); + + source.subscribe(left); + other.subscribe(right); + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + interface JoinSupport extends InnerProducer { + + void innerError(Throwable ex); + + void innerComplete(LeftRightSubscriber sender); + + void innerValue(boolean isLeft, Object o); + + void innerClose(boolean isLeft, LeftRightEndSubscriber index); + + void innerCloseError(Throwable ex); + } + + static final class GroupJoinSubscription + implements JoinSupport { + + final Queue queue; + final BiPredicate queueBiOffer; + + final Disposable.Composite cancellations; + + final Map> lefts; + + final Map rights; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction, ? extends R> resultSelector; + + final Supplier> processorQueueSupplier; + + final CoreSubscriber actual; + + int leftIndex; + + int rightIndex; + + volatile int wip; + + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(GroupJoinSubscription.class, "wip"); + + volatile int active; + + static final AtomicIntegerFieldUpdater ACTIVE = + AtomicIntegerFieldUpdater.newUpdater(GroupJoinSubscription.class, + "active"); + + volatile long requested; + + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(GroupJoinSubscription.class, + "requested"); + + volatile Throwable error; + + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(GroupJoinSubscription.class, + Throwable.class, + "error"); + + static final Integer LEFT_VALUE = 1; + + static final Integer RIGHT_VALUE = 2; + + static final Integer LEFT_CLOSE = 3; + + static final Integer RIGHT_CLOSE = 4; + + @SuppressWarnings("unchecked") + GroupJoinSubscription(CoreSubscriber actual, + Function> leftEnd, + Function> rightEnd, + BiFunction, ? extends R> resultSelector, + Supplier> processorQueueSupplier) { + this.actual = actual; + this.cancellations = Disposables.composite(); + this.processorQueueSupplier = processorQueueSupplier; + this.queue = Queues.unboundedMultiproducer().get(); + this.queueBiOffer = (BiPredicate) queue; + this.lefts = new LinkedHashMap<>(); + this.rights = new LinkedHashMap<>(); + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + ACTIVE.lazySet(this, 2); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public Stream inners() { + return Stream.concat( + lefts.values().stream().map(Scannable::from), + Scannable.from(cancellations).inners() + ); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.CANCELLED) return cancellations.isDisposed(); + if (key == Attr.BUFFERED) return queue.size() / 2; + if (key == Attr.TERMINATED) return active == 0; + if (key == Attr.ERROR) return error; + + return JoinSupport.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + if (cancellations.isDisposed()) { + return; + } + cancellations.dispose(); + if (WIP.getAndIncrement(this) == 0) { + queue.clear(); + } + } + + void errorAll(Subscriber a) { + Throwable ex = Exceptions.terminate(ERROR, this); + + for (Sinks.Many up : lefts.values()) { + up.emitError(ex, Sinks.EmitFailureHandler.FAIL_FAST); + } + + lefts.clear(); + rights.clear(); + + a.onError(ex); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + Queue q = queue; + Subscriber a = actual; + + for (; ; ) { + for (; ; ) { + if (cancellations.isDisposed()) { + q.clear(); + return; + } + + Throwable ex = error; + if (ex != null) { + q.clear(); + cancellations.dispose(); + errorAll(a); + return; + } + + boolean d = active == 0; + + Integer mode = (Integer) q.poll(); + + boolean empty = mode == null; + + if (d && empty) { + for (Sinks.Many up : lefts.values()) { + up.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + + lefts.clear(); + rights.clear(); + cancellations.dispose(); + + a.onComplete(); + return; + } + + if (empty) { + break; + } + + Object val = q.poll(); + + if (mode == LEFT_VALUE) { + @SuppressWarnings("unchecked") TLeft left = (TLeft) val; + + Sinks.Many up = Sinks.unsafe().many().unicast().onBackpressureBuffer(processorQueueSupplier.get()); + int idx = leftIndex++; + lefts.put(idx, up); + + Publisher p; + + try { + p = Objects.requireNonNull(leftEnd.apply(left), + "The leftEnd returned a null Publisher"); + } + catch (Throwable exc) { + Exceptions.addThrowable(ERROR, + this, + Operators.onOperatorError(this, exc, left, + actual.currentContext())); + errorAll(a); + return; + } + + LeftRightEndSubscriber end = + new LeftRightEndSubscriber(this, true, idx); + cancellations.add(end); + + p.subscribe(end); + + ex = error; + if (ex != null) { + cancellations.dispose(); + q.clear(); + errorAll(a); + return; + } + + R w; + + try { + w = Objects.requireNonNull(resultSelector.apply(left, up.asFlux()), + "The resultSelector returned a null value"); + } + catch (Throwable exc) { + Exceptions.addThrowable(ERROR, + this, Operators.onOperatorError(this, exc, up, + actual.currentContext())); + errorAll(a); + return; + } + + // TODO since only left emission calls the actual, it is possible to link downstream backpressure with left's source and not error out + long r = requested; + if (r != 0L) { + a.onNext(w); + Operators.produced(REQUESTED, this, 1); + } + else { + Exceptions.addThrowable(ERROR, + this, + Exceptions.failWithOverflow()); + errorAll(a); + return; + } + + for (TRight right : rights.values()) { + up.emitNext(right, Sinks.EmitFailureHandler.FAIL_FAST); + } + } + else if (mode == RIGHT_VALUE) { + @SuppressWarnings("unchecked") TRight right = (TRight) val; + + int idx = rightIndex++; + + rights.put(idx, right); + + Publisher p; + + try { + p = Objects.requireNonNull(rightEnd.apply(right), + "The rightEnd returned a null Publisher"); + } + catch (Throwable exc) { + Exceptions.addThrowable(ERROR, + this, + Operators.onOperatorError(this, exc, right, + actual.currentContext())); + errorAll(a); + return; + } + + LeftRightEndSubscriber end = + new LeftRightEndSubscriber(this, false, idx); + cancellations.add(end); + + p.subscribe(end); + + ex = error; + if (ex != null) { + q.clear(); + cancellations.dispose(); + errorAll(a); + return; + } + + for (Sinks.Many up : lefts.values()) { + up.emitNext(right, Sinks.EmitFailureHandler.FAIL_FAST); + } + } + else if (mode == LEFT_CLOSE) { + LeftRightEndSubscriber end = (LeftRightEndSubscriber) val; + + Sinks.Many up = lefts.remove(end.index); + cancellations.remove(end); + if (up != null) { + up.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + } + else if (mode == RIGHT_CLOSE) { + LeftRightEndSubscriber end = (LeftRightEndSubscriber) val; + + rights.remove(end.index); + cancellations.remove(end); + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void innerError(Throwable ex) { + if (Exceptions.addThrowable(ERROR, this, ex)) { + ACTIVE.decrementAndGet(this); + drain(); + } + else { + Operators.onErrorDropped(ex, actual.currentContext()); + } + } + + @Override + public void innerComplete(LeftRightSubscriber sender) { + cancellations.remove(sender); + ACTIVE.decrementAndGet(this); + drain(); + } + + @Override + public void innerValue(boolean isLeft, Object o) { + queueBiOffer.test(isLeft ? LEFT_VALUE : RIGHT_VALUE, o); + drain(); + } + + @Override + public void innerClose(boolean isLeft, LeftRightEndSubscriber index) { + queueBiOffer.test(isLeft ? LEFT_CLOSE : RIGHT_CLOSE, index); + drain(); + } + + @Override + public void innerCloseError(Throwable ex) { + if (Exceptions.addThrowable(ERROR, this, ex)) { + drain(); + } + else { + Operators.onErrorDropped(ex, actual.currentContext()); + } + } + } + + static final class LeftRightSubscriber + implements InnerConsumer, Disposable { + + final JoinSupport parent; + + final boolean isLeft; + + volatile Subscription subscription; + + final static AtomicReferenceFieldUpdater + SUBSCRIPTION = + AtomicReferenceFieldUpdater.newUpdater(LeftRightSubscriber.class, + Subscription.class, + "subscription"); + + LeftRightSubscriber(JoinSupport parent, boolean isLeft) { + this.parent = parent; + this.isLeft = isLeft; + } + + @Override + public void dispose() { + Operators.terminate(SUBSCRIPTION, this); + } + + @Override + public Context currentContext() { + return parent.actual().currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return subscription; + if (key == Attr.ACTUAL ) return parent; + if (key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public boolean isDisposed() { + return Operators.cancelledSubscription() == subscription; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(SUBSCRIPTION, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Object t) { + parent.innerValue(isLeft, t); + } + + @Override + public void onError(Throwable t) { + parent.innerError(t); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + } + + static final class LeftRightEndSubscriber + implements InnerConsumer, Disposable { + + final JoinSupport parent; + + final boolean isLeft; + + final int index; + + volatile Subscription subscription; + + final static AtomicReferenceFieldUpdater + SUBSCRIPTION = AtomicReferenceFieldUpdater.newUpdater( + LeftRightEndSubscriber.class, + Subscription.class, + "subscription"); + + LeftRightEndSubscriber(JoinSupport parent, boolean isLeft, int index) { + this.parent = parent; + this.isLeft = isLeft; + this.index = index; + } + + @Override + public void dispose() { + Operators.terminate(SUBSCRIPTION, this); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return subscription; + if (key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public boolean isDisposed() { + return Operators.cancelledSubscription() == subscription; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(SUBSCRIPTION, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Object t) { + if (Operators.terminate(SUBSCRIPTION, this)) { + parent.innerClose(isLeft, this); + } + } + + @Override + public void onError(Throwable t) { + parent.innerError(t); + } + + @Override + public void onComplete() { + parent.innerClose(isLeft, this); + } + + @Override + public Context currentContext() { + return parent.actual().currentContext(); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxHandle.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxHandle.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxHandle.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2016-2021 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.BiConsumer; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Maps the values of the source publisher one-on-one via a handler function as long as the handler function result is + * not null. If the result is a {@code null} value then the source value is filtered rather than mapped. + * + * @param the source value type + * @param the result value type + */ +final class FluxHandle extends InternalFluxOperator { + + final BiConsumer> handler; + + FluxHandle(Flux source, BiConsumer> handler) { + super(source); + this.handler = Objects.requireNonNull(handler, "handler"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof Fuseable.ConditionalSubscriber) { + @SuppressWarnings("unchecked") + Fuseable.ConditionalSubscriber cs = (Fuseable.ConditionalSubscriber) actual; + return new HandleConditionalSubscriber<>(cs, handler); + } + return new HandleSubscriber<>(actual, handler); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class HandleSubscriber + implements InnerOperator, + Fuseable.ConditionalSubscriber, + SynchronousSink { + + final CoreSubscriber actual; + final BiConsumer> handler; + + boolean done; + boolean stop; + Throwable error; + R data; + + Subscription s; + + HandleSubscriber(CoreSubscriber actual, + BiConsumer> handler) { + this.actual = actual; + this.handler = handler; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + try { + handler.accept(t, this); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + reset(); + s.request(1); + } + return; + } + R v = data; + data = null; + if (v != null) { + actual.onNext(v); + } + if(stop){ + if(error != null){ + Throwable e_ = Operators.onNextError(t, error, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + reset(); + s.request(1L); + } + } + else { + s.cancel(); + onComplete(); + } + } + else if(v == null){ + s.request(1L); + } + } + + private void reset() { + done = false; + stop = false; + error = null; + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return false; + } + + try { + handler.accept(t, this); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + return true; + } + else { + reset(); + return false; + } + } + R v = data; + data = null; + if (v != null) { + actual.onNext(v); + } + if(stop){ + if(error != null){ + Throwable e_ = Operators.onNextError(t, error, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + reset(); + return false; + } + } + else { + s.cancel(); + onComplete(); + } + return true; + } + return v != null; + } + + @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; + + actual.onComplete(); + } + + @Override + public void complete() { + if (stop) { + throw new IllegalStateException("Cannot complete after a complete or error"); + } + stop = true; + } + + @Override + public void error(Throwable e) { + if (stop) { + throw new IllegalStateException("Cannot error after a complete or error"); + } + error = Objects.requireNonNull(e, "error"); + stop = true; + } + + @Override + public void next(R o) { + if(data != null){ + throw new IllegalStateException("Cannot emit more than one data"); + } + if (stop) { + throw new IllegalStateException("Cannot emit after a complete or error"); + } + data = Objects.requireNonNull(o, "data"); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } + + static final class HandleConditionalSubscriber + implements Fuseable.ConditionalSubscriber, InnerOperator, + SynchronousSink { + final Fuseable.ConditionalSubscriber actual; + final BiConsumer> handler; + + boolean done; + boolean stop; + Throwable error; + R data; + + Subscription s; + + HandleConditionalSubscriber(Fuseable.ConditionalSubscriber actual, BiConsumer> handler) { + this.actual = actual; + this.handler = handler; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @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; + } + + try { + handler.accept(t, this); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + error = null; + s.request(1); + } + return; + } + R v = data; + data = null; + if (v != null) { + actual.onNext(v); + } + if(stop){ + done = true; //set done because we go through `actual` directly + if(error != null){ + Throwable e_ = Operators.onNextError(t, error, actual.currentContext(), s); + if (e_ != null) { + actual.onError(e_); + } + else { + reset(); //resets done + s.request(1L); + } + } + else { + s.cancel(); + actual.onComplete(); + } + } + else if(v == null){ + s.request(1L); + } + } + + private void reset() { + done = false; + stop = false; + error = null; + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return false; + } + + try { + handler.accept(t, this); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + return true; + } + else { + reset(); + return false; + } + } + R v = data; + boolean emit = false; + data = null; + if (v != null) { + emit = actual.tryOnNext(v); + } + if(stop){ + done = true; //set done because we go through `actual` directly + if(error != null){ + Throwable e_ = Operators.onNextError(t, error, actual.currentContext(), s); + if (e_ != null) { + actual.onError(e_); + } + else { + reset(); //resets done + return false; + } + } + else { + s.cancel(); + actual.onComplete(); + } + return true; + } + return emit; + } + + @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; + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void complete() { + if (stop) { + throw new IllegalStateException("Cannot complete after a complete or error"); + } + stop = true; + } + + @Override + public void error(Throwable e) { + if (stop) { + throw new IllegalStateException("Cannot error after a complete or error"); + } + error = Objects.requireNonNull(e, "error"); + stop = true; + } + + @Override + public void next(R o) { + if(data != null){ + throw new IllegalStateException("Cannot emit more than one data"); + } + if (stop) { + throw new IllegalStateException("Cannot emit after a complete or error"); + } + data = Objects.requireNonNull(o, "data"); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxHandleFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxHandleFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxHandleFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,778 @@ +/* + * Copyright (c) 2016-2021 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.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; + +/** + * Maps the values of the source publisher one-on-one via a handler function. + *

+ * This variant allows composing fuseable stages. + * + * @param the source value type + * @param the result value type + * + * @see Reactive-Streams-Commons + */ +final class FluxHandleFuseable extends InternalFluxOperator implements Fuseable { + + final BiConsumer> handler; + + /** + * Constructs a FluxMap instance with the given source and handler. + * + * @param source the source Publisher instance + * @param handler the handler function + * + * @throws NullPointerException if either {@code source} or {@code handler} is null. + */ + FluxHandleFuseable(Flux source, + BiConsumer> handler) { + super(source); + this.handler = Objects.requireNonNull(handler, "handler"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + @SuppressWarnings("unchecked") + ConditionalSubscriber cs = (ConditionalSubscriber) actual; + return new HandleFuseableConditionalSubscriber<>(cs, handler); + } + return new HandleFuseableSubscriber<>(actual, handler); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class HandleFuseableSubscriber + implements InnerOperator, + ConditionalSubscriber, QueueSubscription, + SynchronousSink { + + final CoreSubscriber actual; + final BiConsumer> handler; + + boolean done; + boolean stop; + Throwable error; + R data; + + QueueSubscription s; + + int sourceMode; + + HandleFuseableSubscriber(CoreSubscriber actual, + BiConsumer> handler) { + this.actual = actual; + this.handler = handler; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return true; + } + + try { + handler.accept(t, this); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + return true; + } + else { + reset(); + return false; + } + } + R v = data; + data = null; + if (v != null) { + actual.onNext(v); + } + if (stop) { + if (error != null) { + Throwable e_ = Operators.onNextError(t, error, actual.currentContext(), s); + if (e_ != null) { + done = true; //set done because we throw or go through `actual` directly + actual.onError(e_); + } + else { + reset(); + return false; + } + } + else { + done = true; //set done because we throw or go through `actual` directly + s.cancel(); + actual.onComplete(); + } + return true; + } + return v != null; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = (QueueSubscription) s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + try { + handler.accept(t, this); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + s.request(1); + } + return; + } + R v = data; + data = null; + if (v != null) { + actual.onNext(v); + } + if (stop) { + if (error != null) { + Throwable e_ = Operators.onNextError(t, error, actual.currentContext(), s); + if (e_ != null) { + done = true; //set done because we throw or go through `actual` directly + actual.onError(e_); + } + else { + reset(); + s.request(1L); + } + } + else{ + done = true; //set done because we throw or go through `actual` directly + s.cancel(); + actual.onComplete(); + } + } + else if (v == null) { + s.request(1L); + } + } + } + + @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; + + actual.onComplete(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + @Nullable + public R poll() { + if (sourceMode == ASYNC) { + if (done) { + return null; + } + long dropped = 0L; + for (; ; ) { + T v = s.poll(); + R u; + if (v != null) { + try { + handler.accept(v, this); + } + catch (Throwable error){ + RuntimeException e_ = Operators.onNextPollError(v, error, actual.currentContext()); + if (e_ != null) { + throw e_; + } + else { + reset(); + continue; + } + } + u = data; + data = null; + if (stop) { + if (error != null) { + RuntimeException e_ = Operators.onNextPollError(v, error, actual.currentContext()); + if (e_ != null) { + done = true; //set done because we throw or go through `actual` directly + throw e_; + } + //else continue + } + else { + done = true; //set done because we throw or go through `actual` directly + s.cancel(); + actual.onComplete(); + } + return u; + } + if (u != null) { + return u; + } + dropped++; + } + else if (dropped != 0L) { + request(dropped); + dropped = 0L; + } + else { + return null; + } + } + } + else { + for (; ; ) { + T v = s.poll(); + if (v != null) { + try { + handler.accept(v, this); + } + catch (Throwable error){ + RuntimeException e_ = Operators.onNextPollError(v, error, actual.currentContext()); + if (e_ != null) { + throw e_; + } + else { + reset(); + continue; + } + } + R u = data; + data = null; + if (stop) { + if (error != null) { + RuntimeException e_ = Operators.onNextPollError(v, error, actual.currentContext()); + if (e_ != null) { + done = true; //set done because we throw or go through `actual` directly + throw e_; + } + else { + reset(); + continue; + } + } + else { + done = true; //set done because we throw or go through `actual` directly + return u; + } + } + if (u != null) { + return u; + } + } + else { + return null; + } + } + } + } + + private void reset() { + done = false; + stop = false; + error = null; + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + + @Override + public void complete() { + if (stop) { + throw new IllegalStateException("Cannot complete after a complete or error"); + } + stop = true; + } + + @Override + public void error(Throwable e) { + if (stop) { + throw new IllegalStateException("Cannot error after a complete or error"); + } + error = Objects.requireNonNull(e, "error"); + stop = true; + } + + @Override + public void next(R o) { + if (data != null) { + throw new IllegalStateException("Cannot emit more than one data"); + } + if (stop) { + throw new IllegalStateException("Cannot emit after a complete or error"); + } + data = Objects.requireNonNull(o, "data"); + } + } + + static final class HandleFuseableConditionalSubscriber + implements ConditionalSubscriber, InnerOperator, + QueueSubscription, SynchronousSink { + + final ConditionalSubscriber actual; + final BiConsumer> handler; + + boolean done; + boolean stop; + Throwable error; + R data; + + QueueSubscription s; + + int sourceMode; + + HandleFuseableConditionalSubscriber(ConditionalSubscriber actual, + BiConsumer> handler) { + this.actual = actual; + this.handler = handler; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = (QueueSubscription) s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + try { + handler.accept(t, this); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + reset(); + s.request(1); + } + return; + } + R v = data; + data = null; + if (v != null) { + actual.onNext(v); + } + if (stop) { + if (error != null) { + Throwable e_ = Operators.onNextError(t, error, actual.currentContext(), s); + if (e_ != null) { + done = true; //set done because we throw or go through `actual` directly + actual.onError(e_); + } + else { + reset(); + s.request(1L); + } + } + else { + done = true; //set done because we throw or go through `actual` directly + s.cancel(); + actual.onComplete(); + } + } + else if (v == null) { + s.request(1L); + } + } + } + + private void reset() { + done = false; + stop = false; + error = null; + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return true; + } + + try { + handler.accept(t, this); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, error, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + return true; + } + else { + reset(); + return false; + } + } + R v = data; + data = null; + boolean emit = false; + if (v != null) { + emit = actual.tryOnNext(v); + } + if (stop) { + if (error != null) { + Throwable e_ = Operators.onNextError(t, error, actual.currentContext(), s); + if (e_ != null) { + done = true; //set done because we throw or go through `actual` directly + actual.onError(e_); + } + else { + reset(); + return false; + } + } + else { + done = true; //set done because we throw or go through `actual` directly + s.cancel(); + actual.onComplete(); + } + return true; + } + return emit; + } + + @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; + + actual.onComplete(); + } + + @Override + public void complete() { + if (stop) { + throw new IllegalStateException("Cannot complete after a complete or error"); + } + stop = true; + } + + @Override + public void error(Throwable e) { + if (stop) { + throw new IllegalStateException("Cannot error after a complete or error"); + } + error = Objects.requireNonNull(e, "error"); + stop = true; + } + + @Override + public void next(R o) { + if (data != null) { + throw new IllegalStateException("Cannot emit more than one data"); + } + if (stop) { + throw new IllegalStateException("Cannot emit after a complete or error"); + } + data = Objects.requireNonNull(o, "data"); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + @Nullable + public R poll() { + if (sourceMode == ASYNC) { + if (done) { + return null; + } + long dropped = 0L; + for (; ; ) { + T v = s.poll(); + R u; + if (v != null) { + try { + handler.accept(v, this); + } + catch (Throwable error){ + RuntimeException e_ = Operators.onNextPollError(v, error, actual.currentContext()); + if (e_ != null) { + throw e_; + } + else { + reset(); + continue; + } + } + u = data; + data = null; + if (stop) { + if (error != null) { + Throwable e_ = Operators.onNextError(v, error, actual.currentContext(), s); + if (e_ != null) { + done = true; //set done because we throw or go through `actual` directly + throw Exceptions.propagate(e_); + } + else { + reset(); + continue; + } + } + else { + done = true; //set done because we throw or go through `actual` directly + s.cancel(); + actual.onComplete(); + } + return u; + } + if (u != null) { + return u; + } + dropped++; + } + else if (dropped != 0L) { + request(dropped); + dropped = 0L; + } + else { + return null; + } + } + } + else { + for (; ; ) { + T v = s.poll(); + if (v != null) { + try { + handler.accept(v, this); + } + catch (Throwable error){ + RuntimeException e_ = Operators.onNextPollError(v, error, actual.currentContext()); + if (e_ != null) { + throw e_; + } + else { + reset(); + continue; + } + } + R u = data; + data = null; + if (stop) { + done = true; //set done because we throw or go through `actual` directly + if (error != null) { + RuntimeException e_ = Operators.onNextPollError(v, error, actual.currentContext()); + if (e_ != null) { + throw e_; + } + else{ + reset(); + continue; + } + } + return u; + } + if (u != null) { + return u; + } + } + else { + return null; + } + } + } + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxHide.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxHide.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxHide.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2016-2021 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.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Hides the identities of the upstream Publisher object and its Subscription + * as well. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxHide extends InternalFluxOperator { + + FluxHide(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new HideSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class HideSubscriber implements InnerOperator { + final CoreSubscriber actual; + + Subscription s; + + HideSubscriber(CoreSubscriber actual) { + this.actual = actual; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + this.s = s; + actual.onSubscribe(this); + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @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); + } + } + + static final class SuppressFuseableSubscriber + implements InnerOperator, Fuseable.QueueSubscription { + + final CoreSubscriber actual; + + Subscription s; + + SuppressFuseableSubscriber(CoreSubscriber actual) { + this.actual = actual; + + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @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 void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public int requestFusion(int requestedMode) { + return Fuseable.NONE; + } + + @Override + @Nullable + public T poll() { + return null; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public void clear() { + + } + + @Override + public int size() { + return 0; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxIndex.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxIndex.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxIndex.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2016-2021 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.BiFunction; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.function.Tuple2; + +/** + * An operator that tags the values it passes through with their index in the original + * sequence as their natural long index (0-based) and maps it to a container type + * by way of a user-provided {@link BiFunction}. Usually the container type would be + * a {@link Tuple2}, t1 value being the index (as mapped by the user function) and t2 the + * source value, but this is entirely up to the user function to decide. + * + * @author Simon Baslé + */ +final class FluxIndex extends InternalFluxOperator { + + private final BiFunction indexMapper; + + FluxIndex(Flux source, + BiFunction indexMapper) { + super(source); + this.indexMapper = NullSafeIndexMapper.create(Objects.requireNonNull(indexMapper, + "indexMapper must be non null")); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + @SuppressWarnings("unchecked") ConditionalSubscriber cs = + (ConditionalSubscriber) actual; + return new IndexConditionalSubscriber<>(cs, indexMapper); + } + else { + return new IndexSubscriber<>(actual, indexMapper); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class IndexSubscriber implements InnerOperator { + + final CoreSubscriber actual; + final BiFunction indexMapper; + + boolean done; + Subscription s; + long index = 0; + + IndexSubscriber(CoreSubscriber actual, + BiFunction indexMapper) { + this.actual = actual; + this.indexMapper = indexMapper; + } + + @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; + } + + long i = this.index; + try { + I typedIndex = indexMapper.apply(i, t); + this.index = i + 1L; + actual.onNext(typedIndex); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + } + } + + @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; + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @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); + } + } + + static final class IndexConditionalSubscriber implements InnerOperator, + ConditionalSubscriber { + + final ConditionalSubscriber actual; + final BiFunction indexMapper; + + Subscription s; + boolean done; + long index; + + IndexConditionalSubscriber( + ConditionalSubscriber cs, + BiFunction indexMapper) { + this.actual = cs; + this.indexMapper = indexMapper; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return true; + } + + I typedIndex; + long i = this.index; + try { + typedIndex = indexMapper.apply(i, t); + this.index = i + 1L; + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return true; + } + + return actual.tryOnNext(typedIndex); + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + long i = this.index; + try { + I typedIndex = indexMapper.apply(i, t); + this.index = i + 1L; + actual.onNext(typedIndex); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + } + } + + @Override + public void onError(Throwable throwable) { + if (done) { + Operators.onErrorDropped(throwable, actual.currentContext()); + return; + } + + done = true; + + actual.onError(throwable); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @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); + } + } + + static class NullSafeIndexMapper implements BiFunction { + + private final BiFunction indexMapper; + + private NullSafeIndexMapper(BiFunction indexMapper) { + this.indexMapper = indexMapper; + } + + @Override + public I apply(Long i, T t) { + I typedIndex = indexMapper.apply(i, t); + if (typedIndex == null) { + throw new NullPointerException("indexMapper returned a null value" + + " at raw index " + i + " for value " + t); + } + return typedIndex; + } + + static BiFunction create( + BiFunction indexMapper) { + if (indexMapper == Flux.TUPLE2_BIFUNCTION) { + // TUPLE2_BIFUNCTION (Tuples::of) never returns null. + // Also helps FluxIndexFuseable to detect the default index mapper. + return indexMapper; + } + return new NullSafeIndexMapper<>(indexMapper); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxIndexFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxIndexFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxIndexFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2016-2021 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.BiFunction; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.function.Tuple2; + +/** + * A {@link reactor.core.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 + * {@link Tuple2}, t1 value being the index (as mapped by the user function) and t2 the + * source value. + * + * @author Simon Baslé + */ +final class FluxIndexFuseable extends InternalFluxOperator + implements Fuseable { + + private final BiFunction indexMapper; + + FluxIndexFuseable(Flux source, + BiFunction indexMapper) { + super(source); + this.indexMapper = FluxIndex.NullSafeIndexMapper.create(Objects.requireNonNull(indexMapper, + "indexMapper must be non null")); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + @SuppressWarnings("unchecked") ConditionalSubscriber cs = + (ConditionalSubscriber) actual; + return new IndexFuseableConditionalSubscriber<>(cs, indexMapper); + } + else { + return new IndexFuseableSubscriber<>(actual, indexMapper); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class IndexFuseableSubscriber implements InnerOperator, + QueueSubscription { + + final CoreSubscriber actual; + final BiFunction indexMapper; + + boolean done; + long index; + QueueSubscription s; + int sourceMode; + + IndexFuseableSubscriber(CoreSubscriber actual, + BiFunction indexMapper) { + this.actual = actual; + this.indexMapper = indexMapper; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + this.s = qs; + + actual.onSubscribe(this); + } + } + + @Override + @Nullable + public I poll() { + T v = s.poll(); + if (v != null) { + long i = this.index; + I indexed = indexMapper.apply(i, v); + this.index = i + 1; + return indexed; + } + return null; + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + long i = this.index; + try { + I indexed = indexMapper.apply(i, t); + this.index = i + 1L; + actual.onNext(indexed); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + } + } + } + + @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; + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if (indexMapper != Flux.TUPLE2_BIFUNCTION && (requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + + @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); + } + } + + static final class IndexFuseableConditionalSubscriber + implements InnerOperator, + ConditionalSubscriber, + QueueSubscription { + + final ConditionalSubscriber actual; + final BiFunction indexMapper; + + boolean done; + long index; + QueueSubscription s; + int sourceMode; + + IndexFuseableConditionalSubscriber( + ConditionalSubscriber cs, + BiFunction indexMapper) { + this.actual = cs; + this.indexMapper = indexMapper; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + this.s = qs; + actual.onSubscribe(this); + } + } + + @Override + @Nullable + public I poll() { + T v = s.poll(); + if (v != null) { + long i = this.index; + I indexed = indexMapper.apply(i, v); + this.index = i + 1; + return indexed; + } + return null; + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return true; + } + + I indexed; + long i = this.index; + try { + indexed = indexMapper.apply(i, t); + this.index = i + 1L; + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return true; + } + + return actual.tryOnNext(indexed); + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + long i = this.index; + try { + I indexed = indexMapper.apply(i, t); + this.index = i + 1L; + actual.onNext(indexed); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + } + } + } + + @Override + public void onError(Throwable throwable) { + if (done) { + Operators.onErrorDropped(throwable, actual.currentContext()); + return; + } + + done = true; + + actual.onError(throwable); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if (indexMapper != Flux.TUPLE2_BIFUNCTION && (requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + + @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); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxInterval.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxInterval.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxInterval.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Scheduler.Worker; +import reactor.util.annotation.Nullable; + +/** + * Periodically emits an ever increasing long value either via a ScheduledExecutorService + * or a custom async callback function + * @see Reactive-Streams-Commons + */ +final class FluxInterval extends Flux implements SourceProducer { + + final Scheduler timedScheduler; + + final long initialDelay; + + final long period; + + final TimeUnit unit; + + FluxInterval( + long initialDelay, + long period, + TimeUnit unit, + Scheduler timedScheduler) { + if (period < 0L) { + throw new IllegalArgumentException("period >= 0 required but it was " + period); + } + this.initialDelay = initialDelay; + this.period = period; + this.unit = Objects.requireNonNull(unit, "unit"); + this.timedScheduler = Objects.requireNonNull(timedScheduler, "timedScheduler"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Worker w = timedScheduler.createWorker(); + + IntervalRunnable r = new IntervalRunnable(actual, w); + + actual.onSubscribe(r); + + try { + w.schedulePeriodically(r, initialDelay, period, unit); + } + catch (RejectedExecutionException ree) { + if (!r.cancelled) { + actual.onError(Operators.onRejectedExecution(ree, r, null, null, + actual.currentContext())); + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return timedScheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return null; + } + + static final class IntervalRunnable implements Runnable, Subscription, + InnerProducer { + final CoreSubscriber actual; + + final Worker worker; + + volatile long requested; + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(IntervalRunnable.class, "requested"); + + long count; + + volatile boolean cancelled; + + IntervalRunnable(CoreSubscriber actual, Worker worker) { + this.actual = actual; + this.worker = worker; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.RUN_ON) return worker; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public void run() { + if (!cancelled) { + if (requested != 0L) { + actual.onNext(count++); + if (requested != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + } else { + cancel(); + + actual.onError(Exceptions.failWithOverflow("Could not emit tick " + count + " due to lack of requests" + + " (interval doesn't support small downstream requests that replenish slower than the ticks)")); + } + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + worker.dispose(); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxIterable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxIterable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxIterable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,769 @@ +/* + * Copyright (c) 2016-2021 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.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.Spliterator; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +import org.reactivestreams.Subscriber; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +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 + * infinite sources (so that said discarding doesn't loop infinitely). + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxIterable extends Flux implements Fuseable, SourceProducer { + + /** + * Utility method to check that a given {@link Iterable} can be positively identified as + * 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. + * + * @param iterable the {@link Iterable} to check. + * @param + * @return true if the {@link Iterable} can confidently classified as finite, false if not finite/unsure + */ + static boolean checkFinite(Iterable iterable) { + return iterable instanceof Collection || iterable.spliterator().hasCharacteristics(Spliterator.SIZED); + } + + final Iterable iterable; + @Nullable + private final Runnable onClose; + + FluxIterable(Iterable iterable, @Nullable Runnable onClose) { + this.iterable = Objects.requireNonNull(iterable, "iterable"); + this.onClose = onClose; + } + + FluxIterable(Iterable iterable) { + this(iterable, null); + } + + @Override + public void subscribe(CoreSubscriber actual) { + boolean knownToBeFinite; + Iterator it; + + try { + knownToBeFinite = FluxIterable.checkFinite(iterable); + it = iterable.iterator(); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + + subscribe(actual, it, knownToBeFinite, onClose); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.BUFFERED) { + if (iterable instanceof Collection) return ((Collection) iterable).size(); + if (iterable instanceof Tuple2) return ((Tuple2) iterable).size(); + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return null; + } + + /** + * Common method to take an {@link Iterator} 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 + */ + static void subscribe(CoreSubscriber s, Iterator it, boolean knownToBeFinite) { + subscribe(s, it, knownToBeFinite, null); + } + + /** + * Common method to take an {@link Iterator} 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 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, + boolean knownToBeFinite, @Nullable Runnable onClose) { + //noinspection ConstantConditions + if (it == null) { + Operators.error(s, new NullPointerException("The iterator is null")); + return; + } + + boolean b; + + try { + b = it.hasNext(); + } + catch (Throwable e) { + Operators.error(s, Operators.onOperatorError(e, s.currentContext())); + if (onClose != null) { + try { + onClose.run(); + } + catch (Throwable t) { + Operators.onErrorDropped(t, s.currentContext()); + } + } + return; + } + if (!b) { + Operators.complete(s); + if (onClose != null) { + try { + onClose.run(); + } + catch (Throwable t) { + Operators.onErrorDropped(t, s.currentContext()); + } + } + return; + } + + if (s instanceof ConditionalSubscriber) { + s.onSubscribe(new IterableSubscriptionConditional<>((ConditionalSubscriber) s, + it, knownToBeFinite, onClose)); + } + else { + s.onSubscribe(new IterableSubscription<>(s, it, knownToBeFinite, onClose)); + } + } + + static final class IterableSubscription + implements InnerProducer, SynchronousSubscription { + + final CoreSubscriber actual; + + final Iterator iterator; + final boolean knownToBeFinite; + final Runnable onClose; + + volatile boolean cancelled; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(IterableSubscription.class, + "requested"); + + int state; + + /** + * Indicates that the iterator's hasNext returned true before but the value is not + * yet retrieved. + */ + static final int STATE_HAS_NEXT_NO_VALUE = 0; + /** + * Indicates that there is a value available in current. + */ + static final int STATE_HAS_NEXT_HAS_VALUE = 1; + /** + * Indicates that there are no more values available. + */ + static final int STATE_NO_NEXT = 2; + /** + * Indicates that the value has been consumed and a new value should be retrieved. + */ + static final int STATE_CALL_HAS_NEXT = 3; + + T current; + Throwable hasNextFailure; + + IterableSubscription(CoreSubscriber actual, + Iterator iterator, boolean knownToBeFinite, @Nullable Runnable onClose) { + this.actual = actual; + this.iterator = iterator; + this.knownToBeFinite = knownToBeFinite; + this.onClose = onClose; + } + + IterableSubscription(CoreSubscriber actual, + Iterator iterator, boolean knownToBeFinite) { + this(actual, iterator, knownToBeFinite, null); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (Operators.addCap(REQUESTED, this, n) == 0) { + if (n == Long.MAX_VALUE) { + fastPath(); + } + else { + slowPath(n); + } + } + } + } + + private void onCloseWithDropError() { + if (onClose != null) { + try { + onClose.run(); + } + catch (Throwable t) { + Operators.onErrorDropped(t, actual.currentContext()); + } + } + } + + void slowPath(long n) { + final Iterator a = iterator; + final Subscriber s = actual; + + long e = 0L; + + for (; ; ) { + + while (e != n) { + T t; + + try { + t = Objects.requireNonNull(a.next(), + "The iterator returned a null value"); + } + catch (Throwable ex) { + s.onError(ex); + onCloseWithDropError(); + return; + } + + if (cancelled) { + return; + } + + s.onNext(t); + + if (cancelled) { + return; + } + + boolean b; + + try { + b = a.hasNext(); + } + catch (Throwable ex) { + s.onError(ex); + onCloseWithDropError(); + return; + } + + if (cancelled) { + return; + } + + if (!b) { + s.onComplete(); + onCloseWithDropError(); + return; + } + + e++; + } + + n = requested; + + if (n == e) { + n = REQUESTED.addAndGet(this, -e); + if (n == 0L) { + return; + } + e = 0L; + } + } + } + + void fastPath() { + final Iterator a = iterator; + final Subscriber s = actual; + + for (; ; ) { + + if (cancelled) { + return; + } + + T t; + + try { + t = Objects.requireNonNull(a.next(), + "The iterator returned a null value"); + } + catch (Exception ex) { + s.onError(ex); + onCloseWithDropError(); + return; + } + + if (cancelled) { + return; + } + + s.onNext(t); + + if (cancelled) { + return; + } + + boolean b; + + try { + b = a.hasNext(); + } + catch (Exception ex) { + s.onError(ex); + onCloseWithDropError(); + return; + } + + if (cancelled) { + return; + } + + if (!b) { + s.onComplete(); + onCloseWithDropError(); + return; + } + } + } + + @Override + public void cancel() { + onCloseWithDropError(); + cancelled = true; + Operators.onDiscardMultiple(this.iterator, this.knownToBeFinite, actual.currentContext()); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.TERMINATED) return state == STATE_NO_NEXT; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public void clear() { + Operators.onDiscardMultiple(this.iterator, this.knownToBeFinite, actual.currentContext()); + state = STATE_NO_NEXT; + } + + @Override + public boolean isEmpty() { + int s = state; + if (s == STATE_NO_NEXT) { + return true; + } + else if (cancelled && !knownToBeFinite) { + return true; //interrupts poll in discard loops due to cancellation + } + else if (s == STATE_HAS_NEXT_HAS_VALUE || s == STATE_HAS_NEXT_NO_VALUE) { + return false; + } + else { + boolean hasNext; + try { + hasNext = iterator.hasNext(); + } + catch (Throwable t) { + //this is a corner case, most Iterators are not expected to throw in hasNext. + //since most calls to isEmpty are in preparation for poll() in fusion, we "defer" + //the exception by pretending queueSub isn't empty, but keeping track of exception + //to be re-thrown by a subsequent call to poll() + state = STATE_HAS_NEXT_NO_VALUE; + hasNextFailure = t; + return false; + } + + if (hasNext) { + state = STATE_HAS_NEXT_NO_VALUE; + return false; + } + state = STATE_NO_NEXT; + return true; + } + } + + @Override + @Nullable + public T poll() { + if (hasNextFailure != null) { + state = STATE_NO_NEXT; + throw Exceptions.propagate(hasNextFailure); + } + if (!isEmpty()) { + T c; + if (state == STATE_HAS_NEXT_NO_VALUE) { + c = iterator.next(); + } + else { + c = current; + current = null; + } + state = STATE_CALL_HAS_NEXT; + if (c == null) { + onCloseWithDropError(); + throw new NullPointerException("iterator returned a null value"); + } + return c; + } + onCloseWithDropError(); + return null; + } + + @Override + public int size() { + if (state == STATE_NO_NEXT) { + return 0; + } + return 1; + } + } + + static final class IterableSubscriptionConditional + implements InnerProducer, SynchronousSubscription { + + final ConditionalSubscriber actual; + + final Iterator iterator; + final boolean knownToBeFinite; + final Runnable onClose; + + volatile boolean cancelled; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(IterableSubscriptionConditional.class, + "requested"); + + int state; + + /** + * Indicates that the iterator's hasNext returned true before but the value is not + * yet retrieved. + */ + static final int STATE_HAS_NEXT_NO_VALUE = 0; + /** + * Indicates that there is a value available in current. + */ + static final int STATE_HAS_NEXT_HAS_VALUE = 1; + /** + * Indicates that there are no more values available. + */ + static final int STATE_NO_NEXT = 2; + /** + * Indicates that the value has been consumed and a new value should be retrieved. + */ + static final int STATE_CALL_HAS_NEXT = 3; + + T current; + + Throwable hasNextFailure; + + IterableSubscriptionConditional(ConditionalSubscriber actual, + Iterator iterator, boolean knownToBeFinite, @Nullable Runnable onClose) { + this.actual = actual; + this.iterator = iterator; + this.knownToBeFinite = knownToBeFinite; + this.onClose = onClose; + } + + IterableSubscriptionConditional(ConditionalSubscriber actual, + Iterator iterator, boolean knownToBeFinite) { + this(actual, iterator, knownToBeFinite, null); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (Operators.addCap(REQUESTED, this, n) == 0) { + if (n == Long.MAX_VALUE) { + fastPath(); + } + else { + slowPath(n); + } + } + } + } + + private void onCloseWithDropError() { + if (onClose != null) { + try { + onClose.run(); + } + catch (Throwable t) { + Operators.onErrorDropped(t, actual.currentContext()); + } + } + } + + void slowPath(long n) { + final Iterator a = iterator; + final ConditionalSubscriber s = actual; + + long e = 0L; + + for (; ; ) { + + while (e != n) { + T t; + + try { + t = Objects.requireNonNull(a.next(), + "The iterator returned a null value"); + } + catch (Throwable ex) { + s.onError(ex); + onCloseWithDropError(); + return; + } + + if (cancelled) { + return; + } + + boolean consumed = s.tryOnNext(t); + + if (cancelled) { + return; + } + + boolean b; + + try { + b = a.hasNext(); + } + catch (Throwable ex) { + s.onError(ex); + onCloseWithDropError(); + return; + } + + if (cancelled) { + return; + } + + if (!b) { + s.onComplete(); + onCloseWithDropError(); + return; + } + + if (consumed) { + e++; + } + } + + n = requested; + + if (n == e) { + n = REQUESTED.addAndGet(this, -e); + if (n == 0L) { + return; + } + e = 0L; + } + } + } + + void fastPath() { + final Iterator a = iterator; + final ConditionalSubscriber s = actual; + + for (; ; ) { + + if (cancelled) { + return; + } + + T t; + + try { + t = Objects.requireNonNull(a.next(), + "The iterator returned a null value"); + } + catch (Exception ex) { + s.onError(ex); + onCloseWithDropError(); + return; + } + + if (cancelled) { + return; + } + + s.tryOnNext(t); + + if (cancelled) { + return; + } + + boolean b; + + try { + b = a.hasNext(); + } + catch (Exception ex) { + s.onError(ex); + onCloseWithDropError(); + return; + } + + if (cancelled) { + return; + } + + if (!b) { + s.onComplete(); + onCloseWithDropError(); + return; + } + } + } + + @Override + public void cancel() { + onCloseWithDropError(); + cancelled = true; + Operators.onDiscardMultiple(this.iterator, this.knownToBeFinite, actual.currentContext()); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.TERMINATED) return state == STATE_NO_NEXT; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public void clear() { + Operators.onDiscardMultiple(this.iterator, this.knownToBeFinite, actual.currentContext()); + state = STATE_NO_NEXT; + } + + @Override + public boolean isEmpty() { + int s = state; + if (s == STATE_NO_NEXT) { + return true; + } + else if (cancelled && !knownToBeFinite) { + return true; //interrupts poll() during discard loop if cancelled + } + else if (s == STATE_HAS_NEXT_HAS_VALUE || s == STATE_HAS_NEXT_NO_VALUE) { + return false; + } + else { + boolean hasNext; + try { + hasNext = iterator.hasNext(); + } + catch (Throwable t) { + //this is a corner case, most Iterators are not expected to throw in hasNext. + //since most calls to isEmpty are in preparation for poll() in fusion, we "defer" + //the exception by pretending queueSub isn't empty, but keeping track of exception + //to be re-thrown by a subsequent call to poll() + state = STATE_HAS_NEXT_NO_VALUE; + hasNextFailure = t; + return false; + } + + if (hasNext) { + state = STATE_HAS_NEXT_NO_VALUE; + return false; + } + state = STATE_NO_NEXT; + return true; + } + } + + @Override + @Nullable + public T poll() { + if (hasNextFailure != null) { + state = STATE_NO_NEXT; + throw Exceptions.propagate(hasNextFailure); + } + if (!isEmpty()) { + T c; + if (state == STATE_HAS_NEXT_NO_VALUE) { + c = iterator.next(); + } + else { + c = current; + current = null; + } + state = STATE_CALL_HAS_NEXT; + return c; + } + onCloseWithDropError(); + return null; + } + + @Override + public int size() { + if (state == STATE_NO_NEXT) { + return 0; + } + return 1; // no way of knowing without enumerating first + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxJoin.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxJoin.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxJoin.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2016-2021 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.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Queue; +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.BiPredicate; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.publisher.FluxGroupJoin.JoinSupport; +import reactor.core.publisher.FluxGroupJoin.LeftRightEndSubscriber; +import reactor.core.publisher.FluxGroupJoin.LeftRightSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; + +/** + * @see https://github.com/reactor/reactive-streams-commons + * @since 3.0 + */ +final class FluxJoin extends + InternalFluxOperator { + + final Publisher other; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction resultSelector; + + FluxJoin(Flux source, + Publisher other, + Function> leftEnd, + Function> rightEnd, + BiFunction resultSelector) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + this.leftEnd = Objects.requireNonNull(leftEnd, "leftEnd"); + this.rightEnd = Objects.requireNonNull(rightEnd, "rightEnd"); + this.resultSelector = Objects.requireNonNull(resultSelector, "resultSelector"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + + JoinSubscription parent = + new JoinSubscription<>(actual, + leftEnd, + rightEnd, + resultSelector); + + actual.onSubscribe(parent); + + LeftRightSubscriber left = new LeftRightSubscriber(parent, true); + parent.cancellations.add(left); + LeftRightSubscriber right = new LeftRightSubscriber(parent, false); + parent.cancellations.add(right); + + source.subscribe(left); + other.subscribe(right); + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class JoinSubscription + implements JoinSupport { + + final Queue queue; + final BiPredicate queueBiOffer; + + final Disposable.Composite cancellations; + + final Map lefts; + + final Map rights; + + final Function> leftEnd; + + final Function> rightEnd; + + final BiFunction resultSelector; + final CoreSubscriber actual; + + volatile int wip; + + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(JoinSubscription.class, "wip"); + + volatile int active; + + static final AtomicIntegerFieldUpdater ACTIVE = + AtomicIntegerFieldUpdater.newUpdater(JoinSubscription.class, + "active"); + + volatile long requested; + + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(JoinSubscription.class, + "requested"); + + volatile Throwable error; + + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(JoinSubscription.class, + Throwable.class, + "error"); + + int leftIndex; + + int rightIndex; + + static final Integer LEFT_VALUE = 1; + + static final Integer RIGHT_VALUE = 2; + + static final Integer LEFT_CLOSE = 3; + + static final Integer RIGHT_CLOSE = 4; + + @SuppressWarnings("unchecked") + JoinSubscription(CoreSubscriber actual, + Function> leftEnd, + Function> rightEnd, + BiFunction resultSelector) { + this.actual = actual; + this.cancellations = Disposables.composite(); + this.queue = Queues.unboundedMultiproducer().get(); + this.queueBiOffer = (BiPredicate) queue; + this.lefts = new LinkedHashMap<>(); + this.rights = new LinkedHashMap<>(); + this.leftEnd = leftEnd; + this.rightEnd = rightEnd; + this.resultSelector = resultSelector; + ACTIVE.lazySet(this, 2); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public Stream inners() { + return Scannable.from(cancellations).inners(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.CANCELLED) return cancellations.isDisposed(); + if (key == Attr.BUFFERED) return queue.size() / 2; + if (key == Attr.TERMINATED) return active == 0; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return JoinSupport.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + if (cancellations.isDisposed()) { + return; + } + cancellations.dispose(); + if (WIP.getAndIncrement(this) == 0) { + queue.clear(); + } + } + + void errorAll(Subscriber a) { + Throwable ex = Exceptions.terminate(ERROR, this); + + lefts.clear(); + rights.clear(); + + a.onError(ex); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + Queue q = queue; + Subscriber a = actual; + + for (; ; ) { + for (; ; ) { + if (cancellations.isDisposed()) { + q.clear(); + return; + } + + Throwable ex = error; + if (ex != null) { + q.clear(); + cancellations.dispose(); + errorAll(a); + return; + } + + boolean d = active == 0; + + Integer mode = (Integer) q.poll(); + + boolean empty = mode == null; + + if (d && empty) { + + lefts.clear(); + rights.clear(); + cancellations.dispose(); + + a.onComplete(); + return; + } + + if (empty) { + break; + } + + Object val = q.poll(); + + if (mode == LEFT_VALUE) { + @SuppressWarnings("unchecked") TLeft left = (TLeft) val; + + int idx = leftIndex++; + lefts.put(idx, left); + + Publisher p; + + try { + p = Objects.requireNonNull(leftEnd.apply(left), + "The leftEnd returned a null Publisher"); + } + catch (Throwable exc) { + Exceptions.addThrowable(ERROR, + this, + Operators.onOperatorError(this, exc, left, + actual.currentContext())); + errorAll(a); + return; + } + + LeftRightEndSubscriber end = + new LeftRightEndSubscriber(this, true, idx); + cancellations.add(end); + + p.subscribe(end); + + ex = error; + if (ex != null) { + q.clear(); + cancellations.dispose(); + errorAll(a); + return; + } + + long r = requested; + long e = 0L; + + for (TRight right : rights.values()) { + + R w; + + try { + w = Objects.requireNonNull(resultSelector.apply(left, + right), + "The resultSelector returned a null value"); + } + catch (Throwable exc) { + Exceptions.addThrowable(ERROR, + this, + Operators.onOperatorError(this, + exc, right, actual.currentContext())); + errorAll(a); + return; + } + + if (e != r) { + a.onNext(w); + + e++; + } + else { + Exceptions.addThrowable(ERROR, + this, + Exceptions.failWithOverflow("Could not " + "emit value due to lack of requests")); + q.clear(); + cancellations.dispose(); + errorAll(a); + return; + } + } + + if (e != 0L) { + Operators.produced(REQUESTED, this, e); + } + } + else if (mode == RIGHT_VALUE) { + @SuppressWarnings("unchecked") TRight right = (TRight) val; + + int idx = rightIndex++; + + rights.put(idx, right); + + Publisher p; + + try { + p = Objects.requireNonNull(rightEnd.apply(right), + "The rightEnd returned a null Publisher"); + } + catch (Throwable exc) { + Exceptions.addThrowable(ERROR, + this, + Operators.onOperatorError(this, exc, right, + actual.currentContext())); + errorAll(a); + return; + } + + LeftRightEndSubscriber end = + new LeftRightEndSubscriber(this, false, idx); + cancellations.add(end); + + p.subscribe(end); + + ex = error; + if (ex != null) { + q.clear(); + cancellations.dispose(); + errorAll(a); + return; + } + + long r = requested; + long e = 0L; + + for (TLeft left : lefts.values()) { + + R w; + + try { + w = Objects.requireNonNull(resultSelector.apply(left, + right), + "The resultSelector returned a null value"); + } + catch (Throwable exc) { + Exceptions.addThrowable(ERROR, + this, + Operators.onOperatorError(this, exc, left, + actual.currentContext())); + errorAll(a); + return; + } + + if (e != r) { + a.onNext(w); + + e++; + } + else { + Exceptions.addThrowable(ERROR, + this, + Exceptions.failWithOverflow("Could not emit " + "value due to lack of requests")); + q.clear(); + cancellations.dispose(); + errorAll(a); + return; + } + } + + if (e != 0L) { + Operators.produced(REQUESTED, this, e); + } + } + else if (mode == LEFT_CLOSE) { + LeftRightEndSubscriber end = (LeftRightEndSubscriber) val; + + lefts.remove(end.index); + cancellations.remove(end); + } + else if (mode == RIGHT_CLOSE) { + LeftRightEndSubscriber end = (LeftRightEndSubscriber) val; + + rights.remove(end.index); + cancellations.remove(end); + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void innerError(Throwable ex) { + if (Exceptions.addThrowable(ERROR, this, ex)) { + ACTIVE.decrementAndGet(this); + drain(); + } + else { + Operators.onErrorDropped(ex, actual.currentContext()); + } + } + + @Override + public void innerComplete(LeftRightSubscriber sender) { + cancellations.remove(sender); + ACTIVE.decrementAndGet(this); + drain(); + } + + @Override + public void innerValue(boolean isLeft, Object o) { + queueBiOffer.test(isLeft ? LEFT_VALUE : RIGHT_VALUE, o); + drain(); + } + + @Override + public void innerClose(boolean isLeft, LeftRightEndSubscriber index) { + queueBiOffer.test(isLeft ? LEFT_CLOSE : RIGHT_CLOSE, index); + drain(); + } + + @Override + public void innerCloseError(Throwable ex) { + if (Exceptions.addThrowable(ERROR, this, ex)) { + drain(); + } + else { + Operators.onErrorDropped(ex, actual.currentContext()); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxJust.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxJust.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxJust.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2015-2021 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 reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * A Stream that emits only one value and then complete. + *

+ * Since the flux retains the value in a final field, any + * {@link this#subscribe(Subscriber)} will + * replay the value. This is a "Cold" fluxion. + *

+ * Create such flux with the provided factory, E.g.: + *

+ * {@code
+ * Flux.just(1).subscribe(
+ *    log::info,
+ *    log::error,
+ *    ()-> log.info("complete")
+ * )
+ * }
+ * 
+ * Will log: + *
+ * {@code
+ * 1
+ * complete
+ * }
+ * 
+ * + * @author Stephane Maldini + */ +final class FluxJust extends Flux + implements Fuseable.ScalarCallable, Fuseable, + SourceProducer { + + final T value; + + FluxJust(T value) { + this.value = Objects.requireNonNull(value, "value"); + } + + @Override + public T call() throws Exception { + return value; + } + + @Override + public void subscribe(final CoreSubscriber actual) { + actual.onSubscribe(Operators.scalarSubscription(actual, value, "just")); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.BUFFERED) return 1; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxLift.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxLift.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxLift.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; + +/** + * @author Stephane Maldini + */ +final class FluxLift extends InternalFluxOperator { + + final Operators.LiftFunction liftFunction; + + FluxLift(Publisher p, + Operators.LiftFunction liftFunction) { + super(Flux.from(p)); + this.liftFunction = liftFunction; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return super.stepName(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); + if (key == Attr.LIFTER) return liftFunction.name; + return super.scanUnsafe(key); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CoreSubscriber input = + liftFunction.lifter.apply(source, actual); + + Objects.requireNonNull(input, "Lifted subscriber MUST NOT be null"); + + return input; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxLiftFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxLiftFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxLiftFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; + +/** + * @author Stephane Maldini + */ +final class FluxLiftFuseable extends InternalFluxOperator + implements Fuseable { + + final Operators.LiftFunction liftFunction; + + FluxLiftFuseable(Publisher p, + Operators.LiftFunction liftFunction) { + super(Flux.from(p)); + this.liftFunction = liftFunction; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return super.stepName(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); + if (key == Attr.LIFTER) return liftFunction.name; + return super.scanUnsafe(key); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CoreSubscriber input = + liftFunction.lifter.apply(source, actual); + + Objects.requireNonNull(input, "Lifted subscriber MUST NOT be null"); + + 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 + return input; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxLimitRequest.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxLimitRequest.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxLimitRequest.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2017-2021 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 org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * @author Simon Baslé + * @author David Karnok + */ +final class FluxLimitRequest extends InternalFluxOperator { + + final long cap; + + FluxLimitRequest(Flux flux, long cap) { + super(flux); + if (cap < 0) { + throw new IllegalArgumentException("cap >= 0 required but it was " + cap); + } + this.cap = cap; + } + + @Override + @Nullable + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (this.cap == 0) { + Operators.complete(actual); + return null; + } + return new FluxLimitRequestSubscriber<>(actual, this.cap); + } + + @Override + public int getPrefetch() { + return 0; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return cap; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + //FluxOperator defines PREFETCH and PARENT + return super.scanUnsafe(key); + } + + static class FluxLimitRequestSubscriber implements InnerOperator { + + final CoreSubscriber actual; + + Subscription parent; + long toProduce; + boolean done; + + volatile long requestRemaining; + static final AtomicLongFieldUpdater REQUEST_REMAINING = + AtomicLongFieldUpdater.newUpdater(FluxLimitRequestSubscriber.class, "requestRemaining"); + + + FluxLimitRequestSubscriber(CoreSubscriber actual, long cap) { + this.actual = actual; + this.toProduce = cap; + this.requestRemaining = cap; + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + long r = toProduce; + if (r > 0L) { + toProduce = --r; + actual.onNext(t); + + if (r == 0) { + done = true; + parent.cancel(); + actual.onComplete(); + } + } + } + + @Override + public void onError(Throwable throwable) { + if (done) { + Operators.onErrorDropped(throwable, currentContext()); + return; + } + done = true; + actual.onError(throwable); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + actual.onComplete(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.parent, s)) { + parent = s; + actual.onSubscribe(this); + } + } + + @Override + public void request(long l) { + for (;;) { + long r = requestRemaining; + long newRequest; + if (r <= l) { + newRequest = r; + } else { + newRequest = l; + } + long u = r - newRequest; + if (REQUEST_REMAINING.compareAndSet(this, r, u)) { + if (newRequest != 0) { + parent.request(newRequest); + } + break; + } + } + } + + @Override + public void cancel() { + parent.cancel(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + //InnerOperator defines ACTUAL + return InnerOperator.super.scanUnsafe(key); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxLog.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxLog.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxLog.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016-2021 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.ConditionalSubscriber; + +/** + * Peek into the lifecycle events and signals of a sequence. + *

+ *

+ * The callbacks are all optional. + *

+ *

+ * Crashes by the lambdas are ignored. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxLog extends InternalFluxOperator { + + final SignalPeek log; + + FluxLog(Flux source, SignalPeek log) { + super(source); + this.log = log; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + @SuppressWarnings("unchecked") // javac, give reason to suppress because inference anomalies + ConditionalSubscriber s2 = (ConditionalSubscriber) actual; + return new FluxPeekFuseable.PeekConditionalSubscriber<>(s2, log); + } + return new FluxPeek.PeekSubscriber<>(actual, log); + } + + @Override + public Object scanUnsafe(Attr key) { + 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/FluxLogFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxLogFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxLogFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Peek into the lifecycle events and signals of a sequence. + *

+ *

+ * The callbacks are all optional. + *

+ *

+ * Crashes by the lambdas are ignored. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxLogFuseable extends InternalFluxOperator + implements Fuseable { + + final SignalPeek log; + + FluxLogFuseable(Flux source, SignalPeek log) { + super(source); + this.log = log; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new FluxPeekFuseable.PeekFuseableConditionalSubscriber<>((ConditionalSubscriber) actual, log); + } + return new FluxPeekFuseable.PeekFuseableSubscriber<>(actual, log); + } + + @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/FluxMap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMap.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Maps the values of the source publisher one-on-one via a mapper function. + * + * @param the source value type + * @param the result value type + * + * @see Reactive-Streams-Commons + */ +final class FluxMap extends InternalFluxOperator { + + final Function mapper; + + /** + * Constructs a FluxMap instance with the given source and mapper. + * + * @param source the source Publisher instance + * @param mapper the mapper function + * + * @throws NullPointerException if either {@code source} or {@code mapper} is null. + */ + FluxMap(Flux source, + Function mapper) { + super(source); + this.mapper = Objects.requireNonNull(mapper, "mapper"); + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof Fuseable.ConditionalSubscriber) { + Fuseable.ConditionalSubscriber cs = + (Fuseable.ConditionalSubscriber) actual; + return new MapConditionalSubscriber<>(cs, mapper); + } + return new MapSubscriber<>(actual, mapper); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class MapSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + final Function mapper; + + boolean done; + + Subscription s; + + MapSubscriber(CoreSubscriber actual, + Function mapper) { + this.actual = actual; + this.mapper = mapper; + } + + @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; + } + + R v; + + try { + v = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null value."); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + s.request(1); + } + return; + } + + actual.onNext(v); + } + + @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; + + actual.onComplete(); + } + + @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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } + + static final class MapConditionalSubscriber + implements Fuseable.ConditionalSubscriber, InnerOperator { + + final Fuseable.ConditionalSubscriber actual; + final Function mapper; + + boolean done; + + Subscription s; + + MapConditionalSubscriber(Fuseable.ConditionalSubscriber actual, + Function mapper) { + this.actual = actual; + this.mapper = mapper; + } + + @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; + } + + R v; + + try { + v = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null value."); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + s.request(1); + } + return; + } + + actual.onNext(v); + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return true; + } + + R v; + + try { + v = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null value."); + return actual.tryOnNext(v); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + done = true; + actual.onError(e_); + return true; + } + else { + return false; + } + } + } + + @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; + + actual.onComplete(); + } + + @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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMapFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMapFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMapFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,413 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Maps the values of the source publisher one-on-one via a mapper function. + *

+ * This variant allows composing fuseable stages. + * + * @param the source value type + * @param the result value type + * + * @see Reactive-Streams-Commons + */ +final class FluxMapFuseable extends InternalFluxOperator implements Fuseable { + + final Function mapper; + + /** + * Constructs a FluxMap instance with the given source and mapper. + * + * @param source the source Publisher instance + * @param mapper the mapper function + * + * @throws NullPointerException if either {@code source} or {@code mapper} is null. + */ + FluxMapFuseable(Flux source, + Function mapper) { + super(source); + this.mapper = Objects.requireNonNull(mapper, "mapper"); + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + ConditionalSubscriber cs = (ConditionalSubscriber) actual; + return new MapFuseableConditionalSubscriber<>(cs, mapper); + } + return new MapFuseableSubscriber<>(actual, mapper); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class MapFuseableSubscriber + implements InnerOperator, + QueueSubscription { + + final CoreSubscriber actual; + final Function mapper; + + boolean done; + + QueueSubscription s; + + int sourceMode; + + MapFuseableSubscriber(CoreSubscriber actual, + Function mapper) { + this.actual = actual; + this.mapper = mapper; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = (QueueSubscription) s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + R v; + + try { + v = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null value."); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + s.request(1); + } + return; + } + + actual.onNext(v); + } + } + + @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; + + actual.onComplete(); + } + + @Override + 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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + @Nullable + public R poll() { + for(;;) { + T v = s.poll(); + if (v != null) { + try { + return Objects.requireNonNull(mapper.apply(v)); + } + catch (Throwable t) { + RuntimeException e_ = Operators.onNextPollError(v, t, currentContext()); + if (e_ != null) { + throw e_; + } + else { + continue; + } + } + } + return null; + } + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + } + + static final class MapFuseableConditionalSubscriber + implements ConditionalSubscriber, InnerOperator, + QueueSubscription { + + final ConditionalSubscriber actual; + final Function mapper; + + boolean done; + + QueueSubscription s; + + int sourceMode; + + MapFuseableConditionalSubscriber(ConditionalSubscriber actual, + Function mapper) { + this.actual = actual; + this.mapper = mapper; + } + + @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); + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = (QueueSubscription) s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + R v; + + try { + v = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null value."); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + } + else { + s.request(1); + } + return; + } + + actual.onNext(v); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return true; + } + + R v; + + try { + v = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null value."); + return actual.tryOnNext(v); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ != null) { + onError(e_); + return true; + } + else { + return false; + } + } + } + + @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; + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + @Nullable + public R poll() { + for(;;) { + T v = s.poll(); + if (v != null) { + try { + return Objects.requireNonNull(mapper.apply(v)); + } + catch (Throwable t) { + RuntimeException e_ = Operators.onNextPollError(v, t, currentContext()); + if (e_ != null) { + throw e_; + } + else { + continue; + } + } + } + return null; + } + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMapSignal.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMapSignal.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMapSignal.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Maps the values of the source publisher one-on-one via a mapper function. + * + * @param the source value type + * @param the result value type + * @author Stephane Maldini + */ +final class FluxMapSignal extends InternalFluxOperator { + + final Function mapperNext; + final Function mapperError; + final Supplier mapperComplete; + + /** + * Constructs a FluxMapSignal instance with the given source and mappers. + * + * @param source the source Publisher instance + * @param mapperNext the next mapper function + * @param mapperError the error mapper function + * @param mapperComplete the complete mapper function + * + * @throws NullPointerException if either {@code source} is null or all {@code mapper} are null. + */ + FluxMapSignal(Flux source, + @Nullable Function mapperNext, + @Nullable Function mapperError, + @Nullable Supplier mapperComplete) { + super(source); + if(mapperNext == null && mapperError == null && mapperComplete == null){ + throw new IllegalArgumentException("Map Signal needs at least one valid mapper"); + } + + this.mapperNext = mapperNext; + this.mapperError = mapperError; + this.mapperComplete = mapperComplete; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxMapSignalSubscriber<>(actual, + mapperNext, + mapperError, + mapperComplete); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class FluxMapSignalSubscriber + extends AbstractQueue + implements InnerOperator, + BooleanSupplier { + + final CoreSubscriber actual; + final Function mapperNext; + final Function mapperError; + final Supplier mapperComplete; + + boolean done; + + Subscription s; + + R value; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(FluxMapSignalSubscriber.class, "requested"); + + volatile boolean cancelled; + + long produced; + + FluxMapSignalSubscriber(CoreSubscriber actual, + @Nullable Function mapperNext, + @Nullable Function mapperError, + @Nullable Supplier mapperComplete) { + this.actual = actual; + this.mapperNext = mapperNext; + this.mapperError = mapperError; + this.mapperComplete = mapperComplete; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + + if (mapperNext == null) { + s.request(Long.MAX_VALUE); + } + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + if (mapperNext == null) { + return; + } + + R v; + + try { + v = Objects.requireNonNull(mapperNext.apply(t), + "The mapper returned a null value."); + } + catch (Throwable e) { + done = true; + actual.onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + + produced++; + actual.onNext(v); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + done = true; + + if(mapperError == null){ + actual.onError(t); + return; + } + + R v; + + try { + v = Objects.requireNonNull(mapperError.apply(t), + "The mapper returned a null value."); + } + catch (Throwable e) { + done = true; + actual.onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + + value = v; + long p = produced; + if (p != 0L) { + Operators.addCap(REQUESTED, this, -p); + } + DrainUtils.postComplete(actual, this, REQUESTED, this, this); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + if(mapperComplete == null){ + actual.onComplete(); + return; + } + + R v; + + try { + v = Objects.requireNonNull(mapperComplete.get(), + "The mapper returned a null value."); + } + catch (Throwable e) { + done = true; + actual.onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + + value = v; + long p = produced; + if (p != 0L) { + Operators.addCap(REQUESTED, this, -p); + } + DrainUtils.postComplete(actual, this, REQUESTED, this, this); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (!DrainUtils.postCompleteRequest(n, actual, this, REQUESTED, this, this)) { + s.request(n); + } + } + } + + @Override + public boolean offer(R e) { + throw new UnsupportedOperationException(); + } + + @Override + @Nullable + public R poll() { + R v = value; + if (v != null) { + value = null; + return v; + } + return null; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return getAsBoolean(); + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.BUFFERED) return size(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + @Nullable + public R peek() { + return value; + } + + @Override + public boolean getAsBoolean() { + return cancelled; + } + + @Override + public void cancel() { + cancelled = true; + s.cancel(); + } + + @Override + public Iterator iterator() { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + return value == null ? 0 : 1; + } + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMaterialize.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMaterialize.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMaterialize.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.BooleanSupplier; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * @author Stephane Maldini + */ +final class FluxMaterialize extends InternalFluxOperator> { + + FluxMaterialize(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new MaterializeSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + final static class MaterializeSubscriber + extends AbstractQueue> + implements InnerOperator>, BooleanSupplier { + + final CoreSubscriber> actual; + final Context cachedContext; + + Signal terminalSignal; + + volatile boolean cancelled; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(MaterializeSubscriber.class, "requested"); + + long produced; + + Subscription s; + + MaterializeSubscriber(CoreSubscriber> subscriber) { + this.actual = subscriber; + this.cachedContext = actual.currentContext(); + } + + @Override + public Context currentContext() { + return cachedContext; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return terminalSignal != null; + if (key == Attr.ERROR) return terminalSignal != null ? terminalSignal.getThrowable() : null; + if (key == Attr.CANCELLED) return getAsBoolean(); + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.BUFFERED) return size(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber> actual() { + return actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T ev) { + if(terminalSignal != null){ + Operators.onNextDropped(ev, this.cachedContext); + return; + } + produced++; + actual.onNext(Signal.next(ev, this.cachedContext)); + } + + @Override + public void onError(Throwable ev) { + if(terminalSignal != null){ + Operators.onErrorDropped(ev, this.cachedContext); + return; + } + terminalSignal = Signal.error(ev, this.cachedContext); + long p = produced; + if (p != 0L) { + Operators.addCap(REQUESTED, this, -p); + } + DrainUtils.postComplete(actual, this, REQUESTED, this, this); + } + + @Override + public void onComplete() { + if(terminalSignal != null){ + return; + } + terminalSignal = Signal.complete(this.cachedContext); + long p = produced; + if (p != 0L) { + Operators.addCap(REQUESTED, this, -p); + } + DrainUtils.postComplete(actual, this, REQUESTED, this, this); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (!DrainUtils.postCompleteRequest(n, actual, this, REQUESTED, this, this)) { + s.request(n); + } + } + } + + @Override + public void cancel() { + if(cancelled){ + return; + } + cancelled = true; + s.cancel(); + } + + @Override + public boolean getAsBoolean() { + return cancelled; + } + + @Override + public boolean offer(Signal e) { + throw new UnsupportedOperationException(); + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + public Signal poll() { + Signal v = terminalSignal; + if (v != null && v != empty) { + terminalSignal = (Signal)empty; + return v; + } + return null; + } + + @Override + @Nullable + public Signal peek() { + return empty == terminalSignal ? null : terminalSignal; + } + + @Override + public Iterator> iterator() { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + return terminalSignal == null || terminalSignal == empty ? 0 : 1; + } + + @Override + public String toString() { + return "MaterializeSubscriber"; + } + + static final Signal empty = new ImmutableSignal<>(Context.empty(), SignalType.ON_NEXT, null, null, null); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMerge.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMerge.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMerge.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016-2021 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.function.IntFunction; +import java.util.function.Supplier; + +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; + +/** + * Merges a fixed array of Publishers. + * @param the element type of the publishers + * @see Reactive-Streams-Commons + */ +final class FluxMerge extends Flux implements SourceProducer { + + final Publisher[] sources; + + final boolean delayError; + + final int maxConcurrency; + + final Supplier> mainQueueSupplier; + + final int prefetch; + + final Supplier> innerQueueSupplier; + + FluxMerge(Publisher[] sources, + boolean delayError, int maxConcurrency, + Supplier> mainQueueSupplier, + int prefetch, Supplier> innerQueueSupplier) { + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + if (maxConcurrency <= 0) { + throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); + } + this.sources = Objects.requireNonNull(sources, "sources"); + this.delayError = delayError; + this.maxConcurrency = maxConcurrency; + this.prefetch = prefetch; + this.mainQueueSupplier = Objects.requireNonNull(mainQueueSupplier, "mainQueueSupplier"); + this.innerQueueSupplier = Objects.requireNonNull(innerQueueSupplier, "innerQueueSupplier"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + FluxFlatMap.FlatMapMain, T> merger = new FluxFlatMap.FlatMapMain<>( + actual, identityFunction(), delayError, maxConcurrency, mainQueueSupplier, prefetch, + innerQueueSupplier); + + merger.onSubscribe(new FluxArray.ArraySubscription<>(merger, sources)); + } + + /** + * Returns a new instance which has the additional source to be merged together with + * the current array of sources. + *

+ * This operation doesn't change the current FluxMerge instance. + * + * @param source the new source to merge with the others + * @param newQueueSupplier a function that should return a new queue supplier based on the change in the maxConcurrency value + * @return the new FluxMerge instance + */ + FluxMerge mergeAdditionalSource(Publisher source, IntFunction>> newQueueSupplier) { + int n = sources.length; + @SuppressWarnings("unchecked") + Publisher[] newArray = new Publisher[n + 1]; + System.arraycopy(sources, 0, newArray, 0, n); + newArray[n] = source; + + // increase the maxConcurrency because if merged separately, it would have run concurrently anyway + Supplier> newMainQueue; + int mc = maxConcurrency; + if (mc != Integer.MAX_VALUE) { + mc++; + newMainQueue = newQueueSupplier.apply(mc); + } else { + newMainQueue = mainQueueSupplier; + } + + return new FluxMerge<>(newArray, delayError, mc, newMainQueue, prefetch, innerQueueSupplier); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeComparing.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeComparing.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeComparing.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2018-2021 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.Arrays; +import java.util.Comparator; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * 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}. + * + * @param the value type + * @author David Karnok + * @author Simon Baslé + */ +//source: https://akarnokd.blogspot.fr/2017/09/java-9-flow-api-ordered-merge.html +final class FluxMergeComparing extends Flux implements SourceProducer { + + final int prefetch; + final Comparator valueComparator; + final Publisher[] sources; + final boolean delayError; + + @SafeVarargs + FluxMergeComparing(int prefetch, Comparator valueComparator, boolean delayError, Publisher... sources) { + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.sources = Objects.requireNonNull(sources, "sources must be non-null"); + + for (int i = 0; i < sources.length; i++) { + Publisher source = sources[i]; + if (source == null) { + throw new NullPointerException("sources[" + i + "] is null"); + } + } + + this.prefetch = prefetch; + this.valueComparator = valueComparator; + this.delayError = delayError; + } + + /** + * Returns a new instance which has the additional source to be merged together with + * the current array of sources. The provided {@link Comparator} is tested for equality + * with the current comparator, and if different is combined by {@link Comparator#thenComparing(Comparator)}. + *

+ * This operation doesn't change the current {@link FluxMergeComparing} instance. + * + * @param source the new source to merge with the others + * @return the new {@link FluxMergeComparing} instance + */ + FluxMergeComparing mergeAdditionalSource(Publisher source, Comparator otherComparator) { + int n = sources.length; + @SuppressWarnings("unchecked") + Publisher[] newArray = new Publisher[n + 1]; + System.arraycopy(sources, 0, newArray, 0, n); + newArray[n] = source; + + if (!valueComparator.equals(otherComparator)) { + @SuppressWarnings("unchecked") + Comparator currentComparator = (Comparator) this.valueComparator; + final Comparator newComparator = currentComparator.thenComparing(otherComparator); + return new FluxMergeComparing<>(prefetch, newComparator, delayError, newArray); + } + return new FluxMergeComparing<>(prefetch, valueComparator, delayError, newArray); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return sources.length > 0 ? sources[0] : null; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void subscribe(CoreSubscriber actual) { + MergeOrderedMainProducer main = new MergeOrderedMainProducer<>(actual, valueComparator, prefetch, sources.length, delayError); + actual.onSubscribe(main); + main.subscribe(sources); + } + + + static final class MergeOrderedMainProducer implements InnerProducer { + + static final Object DONE = new Object(); + + final CoreSubscriber actual; + final MergeOrderedInnerSubscriber[] subscribers; + final Comparator comparator; + final Object[] values; + final boolean delayError; + + boolean done; + + volatile Throwable error; + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(MergeOrderedMainProducer.class, Throwable.class, "error"); + + volatile int cancelled; + static final AtomicIntegerFieldUpdater CANCELLED = + AtomicIntegerFieldUpdater.newUpdater(MergeOrderedMainProducer.class, "cancelled"); + + volatile long requested; + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(MergeOrderedMainProducer.class, "requested"); + + volatile long emitted; + static final AtomicLongFieldUpdater EMITTED = + AtomicLongFieldUpdater.newUpdater(MergeOrderedMainProducer.class, "emitted"); + + volatile int wip; + + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(MergeOrderedMainProducer.class, "wip"); + + MergeOrderedMainProducer(CoreSubscriber actual, + Comparator comparator, int prefetch, int n, boolean delayError) { + this.actual = actual; + this.comparator = comparator; + this.delayError = delayError; + + @SuppressWarnings("unchecked") + MergeOrderedInnerSubscriber[] mergeOrderedInnerSub = + new MergeOrderedInnerSubscriber[n]; + this.subscribers = mergeOrderedInnerSub; + + for (int i = 0; i < n; i++) { + this.subscribers[i] = new MergeOrderedInnerSubscriber<>(this, prefetch); + } + this.values = new Object[n]; + } + + void subscribe(Publisher[] sources) { + if (sources.length != subscribers.length) { + throw new IllegalArgumentException("must subscribe with " + subscribers.length + " sources"); + } + for (int i = 0; i < sources.length; i++) { + Objects.requireNonNull(sources[i], "subscribed with a null source: sources[" + i + "]"); + sources[i].subscribe(subscribers[i]); + } + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void request(long n) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + + @Override + public void cancel() { + if (CANCELLED.compareAndSet(this, 0, 1)) { + for (MergeOrderedInnerSubscriber subscriber : subscribers) { + subscriber.cancel(); + } + + if (WIP.getAndIncrement(this) == 0) { + discardData(); + } + } + } + + void onInnerError(MergeOrderedInnerSubscriber inner, Throwable ex) { + Throwable e = Operators.onNextInnerError(ex, actual().currentContext(), this); + if (e != null) { + if (Exceptions.addThrowable(ERROR, this, e)) { + if (!delayError) { + done = true; + } + inner.done = true; + drain(); + } + else { + inner.done = true; + Operators.onErrorDropped(e, actual.currentContext()); + } + } + else { + inner.done = true; + drain(); + } + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + CoreSubscriber actual = this.actual; + Comparator comparator = this.comparator; + + MergeOrderedInnerSubscriber[] subscribers = this.subscribers; + int n = subscribers.length; + + Object[] values = this.values; + + long e = emitted; + + for (;;) { + long r = requested; + + for (;;) { + boolean d = this.done; + if (cancelled != 0) { + Arrays.fill(values, null); + + for (MergeOrderedInnerSubscriber inner : subscribers) { + inner.queue.clear(); + } + return; + } + + int innerDoneCount = 0; + int nonEmpty = 0; + for (int i = 0; i < n; i++) { + Object o = values[i]; + if (o == DONE) { + innerDoneCount++; + nonEmpty++; + } + else if (o == null) { + boolean innerDone = subscribers[i].done; + o = subscribers[i].queue.poll(); + if (o != null) { + values[i] = o; + nonEmpty++; + } + else if (innerDone) { + values[i] = DONE; + innerDoneCount++; + nonEmpty++; + } + } + else { + nonEmpty++; + } + } + + if (checkTerminated(d || innerDoneCount == n, actual)) { + return; + } + + if (nonEmpty != n || e >= r) { + break; + } + + T min = null; + int minIndex = -1; + + int i = 0; + for (Object o : values) { + if (o != DONE) { + boolean smaller; + try { + @SuppressWarnings("unchecked") + T t = (T) o; + smaller = min == null || comparator.compare(min, t) > 0; + } catch (Throwable ex) { + Exceptions.addThrowable(ERROR, this, ex); + cancel(); + actual.onError(Exceptions.terminate(ERROR, this)); + return; + } + if (smaller) { + @SuppressWarnings("unchecked") + T t = (T) o; + min = t; + minIndex = i; + } + } + i++; + } + + values[minIndex] = null; + + actual.onNext(min); + + e++; + subscribers[minIndex].request(1); + } + + this.emitted = e; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, Subscriber a) { + if (cancelled != 0) { + discardData(); + return true; + } + + if (!d) { + return false; + } + + if (delayError) { + Throwable e = error; + if (e != null && e != Exceptions.TERMINATED) { + e = Exceptions.terminate(ERROR, this); + a.onError(e); + } + else { + a.onComplete(); + } + } + else { + Throwable e = error; + if (e != null && e != Exceptions.TERMINATED) { + e = Exceptions.terminate(ERROR, this); + cancel(); + discardData(); + a.onError(e); + } + else { + a.onComplete(); + } + } + return true; + } + + private void discardData() { + Context ctx = actual().currentContext(); + for (Object v : values) { + if (v != DONE) { + Operators.onDiscard(v, ctx); + } + } + Arrays.fill(values, null); + for (MergeOrderedInnerSubscriber subscriber : subscribers) { + Operators.onDiscardQueueWithClear(subscriber.queue, ctx, null); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) return actual; + if (key == Attr.CANCELLED) return this.cancelled > 0; + if (key == Attr.ERROR) return this.error; + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested - emitted; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } + + static final class MergeOrderedInnerSubscriber implements InnerOperator { + + final MergeOrderedMainProducer parent; + final int prefetch; + final int limit; + final Queue queue; + + int consumed; + + volatile boolean done; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(MergeOrderedInnerSubscriber.class, Subscription.class, "s"); + + MergeOrderedInnerSubscriber(MergeOrderedMainProducer parent, int prefetch) { + this.parent = parent; + this.prefetch = prefetch; + this.limit = prefetch - (prefetch >> 2); + this.queue = Queues.get(prefetch).get(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(prefetch); + } + } + + @Override + public void onNext(T item) { + if (parent.done || done) { + Operators.onNextDropped(item, actual().currentContext()); + return; + } + queue.offer(item); + parent.drain(); + } + + @Override + public void onError(Throwable throwable) { + parent.onInnerError(this, throwable); + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + + /** + * @param n is ignored and considered to be 1 + */ + @Override + public void request(long n) { + int c = consumed + 1; + if (c == limit) { + consumed = 0; + Subscription sub = s; + if (sub != this) { + sub.request(c); + } + } + else { + consumed = c; + } + } + + @Override + public void cancel() { + Subscription sub = S.getAndSet(this, this); + if (sub != null && sub != this) { + sub.cancel(); + } + } + + @Override + public CoreSubscriber actual() { + return parent.actual; //TODO check + } + + @Override + @Nullable + public Object scanUnsafe(Attr key){ + if (key == Attr.ACTUAL) return parent; //TODO does that check out? + if (key == Attr.PARENT) return s; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.TERMINATED) return done; + if (key == Attr.BUFFERED) return queue.size(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeSequential.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeSequential.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMergeSequential.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.Fuseable.QueueSubscription; +import reactor.core.Scannable; +import reactor.core.publisher.FluxConcatMap.ErrorMode; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * Maps each upstream value into a Publisher and concatenates them into one + * sequence of items. + * + * @param the source value type + * @param the output value type + * @see Reactive-Streams-Commons + */ +final class FluxMergeSequential extends InternalFluxOperator { + + final ErrorMode errorMode; + + final Function> mapper; + + final int maxConcurrency; + + final int prefetch; + + final Supplier>> queueSupplier; + + FluxMergeSequential(Flux source, + Function> mapper, + int maxConcurrency, int prefetch, ErrorMode errorMode) { + this(source, mapper, maxConcurrency, prefetch, errorMode, + Queues.get(Math.max(prefetch, maxConcurrency))); + } + + //for testing purpose + FluxMergeSequential(Flux source, + Function> mapper, + int maxConcurrency, int prefetch, ErrorMode errorMode, + Supplier>> queueSupplier) { + super(source); + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + if (maxConcurrency <= 0) { + throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); + } + this.mapper = Objects.requireNonNull(mapper, "mapper"); + this.maxConcurrency = maxConcurrency; + this.prefetch = prefetch; + this.errorMode = errorMode; + this.queueSupplier = queueSupplier; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + //for now mergeSequential doesn't support onErrorContinue, so the scalar version shouldn't either + if (FluxFlatMap.trySubscribeScalarMap(source, actual, mapper, false, false)) { + return null; + } + + return new MergeSequentialMain(actual, + mapper, + maxConcurrency, + prefetch, + errorMode, + queueSupplier); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class MergeSequentialMain implements InnerOperator { + + /** the mapper giving the inner publisher for each source value */ + final Function> mapper; + + /** how many eagerly subscribed inner stream at a time, at most */ + final int maxConcurrency; + + /** request size for inner subscribers (size of the inner queues) */ + final int prefetch; + + final Queue> subscribers; + + /** whether or not errors should be delayed until the very end of all inner + * publishers or just until the completion of the currently merged inner publisher + */ + final ErrorMode errorMode; + + final CoreSubscriber actual; + + Subscription s; + + volatile boolean done; + + volatile boolean cancelled; + + volatile Throwable error; + + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(MergeSequentialMain.class, Throwable.class, "error"); + + MergeSequentialInner current; + + /** guard against multiple threads entering the drain loop. allows thread + * stealing by continuing the loop if wip has been incremented externally by + * a separate thread. */ + volatile int wip; + + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(MergeSequentialMain.class, "wip"); + + volatile long requested; + + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(MergeSequentialMain.class, "requested"); + + MergeSequentialMain(CoreSubscriber actual, + Function> mapper, + int maxConcurrency, int prefetch, ErrorMode errorMode, + Supplier>> queueSupplier) { + this.actual = actual; + this.mapper = mapper; + this.maxConcurrency = maxConcurrency; + this.prefetch = prefetch; + this.errorMode = errorMode; + this.subscribers = queueSupplier.get(); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public Stream inners() { + return Stream.of(subscribers.peek()); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ERROR) return error; + if (key == Attr.TERMINATED) return done && subscribers.isEmpty(); + if (key == Attr.DELAY_ERROR) return errorMode != ErrorMode.IMMEDIATE; + if (key == Attr.PREFETCH) return maxConcurrency; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.BUFFERED) return subscribers.size(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + 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(maxConcurrency == Integer.MAX_VALUE ? Long.MAX_VALUE : + maxConcurrency); + } + } + + @Override + public void onNext(T t) { + Publisher publisher; + + try { + publisher = Objects.requireNonNull(mapper.apply(t), "publisher"); + } + catch (Throwable ex) { + onError(Operators.onOperatorError(s, ex, t, actual.currentContext())); + return; + } + + MergeSequentialInner inner = new MergeSequentialInner<>(this, prefetch); + + if (cancelled) { + return; + } + + if (!subscribers.offer(inner)) { + int badSize = subscribers.size(); + inner.cancel(); + drainAndCancel(); + onError(Operators.onOperatorError(s, + new IllegalStateException("Too many subscribers for " + + "fluxMergeSequential on item: " + t + + "; subscribers: " + badSize), + t, actual.currentContext())); + return; + } + + if (cancelled) { + return; + } + + publisher.subscribe(inner); + + if (cancelled) { + inner.cancel(); + drainAndCancel(); + } + } + + @Override + public void onError(Throwable t) { + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(); + } + else { + Operators.onErrorDropped(t, actual.currentContext()); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + s.cancel(); + + drainAndCancel(); + } + + void drainAndCancel() { + if (WIP.getAndIncrement(this) == 0) { + do { + cancelAll(); + } + while (WIP.decrementAndGet(this) != 0); + } + } + + void cancelAll() { + MergeSequentialInner c = this.current; + if (c != null) { + c.cancel(); + } + + MergeSequentialInner inner; + while ((inner = subscribers.poll()) != null) { + inner.cancel(); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + void innerNext(MergeSequentialInner inner, R value) { + if (inner.queue().offer(value)) { + drain(); + } + else { + inner.cancel(); + onError(Operators.onOperatorError(null, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), value, + actual.currentContext())); + } + } + + void innerError(MergeSequentialInner inner, Throwable e) { + if (Exceptions.addThrowable(ERROR, this, e)) { + inner.setDone(); + if (errorMode != ErrorMode.END) { + s.cancel(); + } + drain(); + } + else { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + void innerComplete(MergeSequentialInner inner) { + inner.setDone(); + drain(); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + MergeSequentialInner inner = current; + Subscriber a = actual; + ErrorMode em = errorMode; + + for (; ; ) { + long r = requested; + long e = 0L; + + if (inner == null) { + + if (em != ErrorMode.END) { + Throwable ex = error; + if (ex != null) { + cancelAll(); + + a.onError(ex); + return; + } + } + + boolean outerDone = done; + + inner = subscribers.poll(); + + if (outerDone && inner == null) { + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + if (inner != null) { + current = inner; + } + } + + boolean continueNextSource = false; + + if (inner != null) { + Queue q = inner.queue(); + //noinspection ConstantConditions + if (q != null) { + while (e != r) { + if (cancelled) { + cancelAll(); + return; + } + + if (em == ErrorMode.IMMEDIATE) { + Throwable ex = error; + if (ex != null) { + current = null; + inner.cancel(); + cancelAll(); + + a.onError(ex); + return; + } + } + + boolean d = inner.isDone(); + + R v; + + try { + v = q.poll(); + } + catch (Throwable ex) { + current = null; + inner.cancel(); + ex = Operators.onOperatorError(ex, + actual.currentContext()); + cancelAll(); + a.onError(ex); + return; + } + + boolean empty = v == null; + + if (d && empty) { + inner = null; + current = null; + s.request(1); + continueNextSource = true; + break; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + + inner.requestOne(); + } + + if (e == r) { + if (cancelled) { + cancelAll(); + return; + } + + if (em == ErrorMode.IMMEDIATE) { + Throwable ex = error; + if (ex != null) { + current = null; + inner.cancel(); + cancelAll(); + + a.onError(ex); + return; + } + } + + boolean d = inner.isDone(); + + boolean empty = q.isEmpty(); + + if (d && empty) { + inner = null; + current = null; + s.request(1); + continueNextSource = true; + } + } + } + } + + if (e != 0L && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + if (continueNextSource) { + continue; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + } + + /** + * Represents the inner flux in a mergeSequential, that has an internal queue to + * hold items while they arrive out of order. The queue is drained as soon as correct + * order can be restored. + * @param the type of objects emitted by the inner flux + */ + static final class MergeSequentialInner implements InnerConsumer{ + + final MergeSequentialMain parent; + + final int prefetch; + + final int limit; + + volatile Queue queue; + + volatile Subscription subscription; + + static final AtomicReferenceFieldUpdater + SUBSCRIPTION = AtomicReferenceFieldUpdater.newUpdater( + MergeSequentialInner.class, Subscription.class, "subscription"); + + volatile boolean done; + + long produced; + + int fusionMode; + + MergeSequentialInner(MergeSequentialMain parent, int prefetch) { + this.parent = parent; + this.prefetch = prefetch; + this.limit = Operators.unboundedOrLimit(prefetch); + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return subscription; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.TERMINATED) return done && (queue == null || queue.isEmpty()); + if (key == Attr.CANCELLED) return subscription == Operators.cancelledSubscription(); + if (key == Attr.BUFFERED) return queue == null ? 0 : queue.size(); + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(SUBSCRIPTION, this, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(Fuseable.ANY | Fuseable.THREAD_BARRIER); + if (m == Fuseable.SYNC) { + fusionMode = m; + queue = qs; + done = true; + parent.innerComplete(this); + return; + } + if (m == Fuseable.ASYNC) { + fusionMode = m; + queue = qs; + s.request(Operators.unboundedOrPrefetch(prefetch)); + return; + } + } + + queue = Queues.get(prefetch).get(); + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(R t) { + if (fusionMode == Fuseable.NONE) { + parent.innerNext(this, t); + } else { + parent.drain(); + } + } + + @Override + public void onError(Throwable t) { + parent.innerError(this, t); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void requestOne() { + if (fusionMode != Fuseable.SYNC) { + long p = produced + 1; + if (p == limit) { + produced = 0L; + subscription.request(p); + } else { + produced = p; + } + } + } + + + void cancel() { + Operators.set(SUBSCRIPTION, this, Operators.cancelledSubscription()); + } + + boolean isDone() { + return done; + } + + void setDone() { + this.done = true; + } + + Queue queue() { + return queue; + } + } + +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMetrics.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMetrics.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMetrics.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2018-2021 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.LinkedList; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +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. + * + * @implNote Metrics.isInstrumentationAvailable() test should be performed BEFORE instantiating or referencing this + * class, otherwise a {@link NoClassDefFoundError} will be thrown if Micrometer is not there. + * + * @author Simon Baslé + * @author Stephane Maldini + */ +final class FluxMetrics extends InternalFluxOperator { + + final String name; + final Tags tags; + + //Note: meters and tag names are normalized by micrometer on the basis that the word + // separator is the dot, not camelCase... + final MeterRegistry registryCandidate; + + FluxMetrics(Flux flux) { + super(flux); + + this.name = resolveName(flux); + this.tags = resolveTags(flux, DEFAULT_TAGS_FLUX); + + this.registryCandidate = MicrometerConfiguration.getRegistry(); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new MetricsSubscriber<>(actual, registryCandidate, Clock.SYSTEM, this.name, this.tags); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static class MetricsSubscriber implements InnerOperator { + + final CoreSubscriber actual; + final Clock clock; + final String sequenceName; + final Tags commonTags; + final MeterRegistry registry; + final DistributionSummary requestedCounter; + final Timer onNextIntervalTimer; + + Timer.Sample subscribeToTerminateSample; + long lastNextEventNanos = -1L; + boolean done; + Subscription s; + + MetricsSubscriber(CoreSubscriber actual, + MeterRegistry registry, + Clock clock, + String sequenceName, + Tags commonTags) { + this.actual = actual; + this.clock = clock; + this.sequenceName = sequenceName; + this.commonTags = commonTags; + this.registry = registry; + + + this.onNextIntervalTimer = Timer.builder(sequenceName + METER_ON_NEXT_DELAY) + .tags(commonTags) + .description( + "Measures delays between onNext signals (or between onSubscribe and first onNext)") + .register(registry); + + if (!REACTOR_DEFAULT_NAME.equals(sequenceName)) { + this.requestedCounter = DistributionSummary.builder(sequenceName + METER_REQUESTED) + .tags(commonTags) + .description( + "Counts the amount requested to a named Flux by all subscribers, until at least one requests an unbounded amount") + .register(registry); + } + else { + requestedCounter = null; + } + } + + @Override + final public CoreSubscriber actual() { + return actual; + } + + @Override + final public void cancel() { + //we don't record the time between last onNext and cancel, + // because it would skew the onNext count by one + recordCancel(sequenceName, commonTags, registry, subscribeToTerminateSample); + + s.cancel(); + } + + @Override + final public void onComplete() { + if (done) { + return; + } + done = true; + + //we don't record the time between last onNext and onComplete, + // because it would skew the onNext count by one. + // We differentiate between empty completion and value completion, however, via tags. + if (this.onNextIntervalTimer.count() == 0) { + recordOnCompleteEmpty(sequenceName, commonTags, registry, subscribeToTerminateSample); + } else { + recordOnComplete(sequenceName, commonTags, registry, subscribeToTerminateSample); + } + + actual.onComplete(); + } + + @Override + final public void onError(Throwable e) { + if (done) { + recordMalformed(sequenceName, commonTags, registry); + Operators.onErrorDropped(e, actual.currentContext()); + return; + } + done = true; + //we don't record the time between last onNext and onError, + // because it would skew the onNext count by one + recordOnError(sequenceName, commonTags, registry, subscribeToTerminateSample, e); + actual.onError(e); + } + + @Override + public void onNext(T t) { + if (done) { + recordMalformed(sequenceName, commonTags, registry); + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + //record the delay since previous onNext/onSubscribe. This also records the count. + long last = this.lastNextEventNanos; + this.lastNextEventNanos = clock.monotonicTime(); + this.onNextIntervalTimer.record(lastNextEventNanos - last, TimeUnit.NANOSECONDS); + + actual.onNext(t); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + recordOnSubscribe(sequenceName, commonTags, registry); + this.subscribeToTerminateSample = Timer.start(clock); + this.lastNextEventNanos = clock.monotonicTime(); + this.s = s; + actual.onSubscribe(this); + + } + } + + @Override + final public void request(long l) { + if (Operators.validate(l)) { + if (requestedCounter != null) { + requestedCounter.record(l); + } + s.request(l); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return InnerOperator.super.scanUnsafe(key); + } + } + + /** + * The default sequence name that will be used for instrumented {@link Flux} and {@link Mono} that don't have a + * {@link Flux#name(String) name}. + */ + static final String REACTOR_DEFAULT_NAME = "reactor"; + /** + * Meter that counts the number of events received from a malformed source (ie an onNext after an onComplete). + */ + static final String METER_MALFORMED = ".malformed.source"; + /** + * Meter that counts the number of subscriptions to a sequence. + */ + static final String METER_SUBSCRIBED = ".subscribed"; + /** + * Meter that times the duration elapsed between a subscription and the termination or cancellation of the sequence. + * A status tag is added to specify what event caused the timer to end (completed, completedEmpty, error, cancelled). + */ + static final String METER_FLOW_DURATION = ".flow.duration"; + /** + * Meter that times the delays between each onNext (or between the first onNext and the onSubscribe event). + */ + static final String METER_ON_NEXT_DELAY = ".onNext.delay"; + /** + * Meter that tracks the request amount, in {@link Flux#name(String) named} sequences only. + */ + static final String METER_REQUESTED = ".requested"; + /** + * Tag used by {@link #METER_FLOW_DURATION} when "status" is {@link #TAG_ON_ERROR}, to store the + * exception that occurred. + */ + static final String TAG_KEY_EXCEPTION = "exception"; + /** + * Tag bearing the sequence's name, as given by the {@link Flux#name(String)} operator. + */ + static final Tags DEFAULT_TAGS_FLUX = Tags.of("type", "Flux"); + static final Tags DEFAULT_TAGS_MONO = Tags.of("type", "Mono"); + + // === Operator === + static final Tag TAG_ON_ERROR = Tag.of("status", "error"); + static final Tags TAG_ON_COMPLETE = Tags.of("status", "completed", TAG_KEY_EXCEPTION, ""); + static final Tags TAG_ON_COMPLETE_EMPTY = Tags.of("status", "completedEmpty", TAG_KEY_EXCEPTION, ""); + static final Tags TAG_CANCEL = Tags.of("status", "cancelled", TAG_KEY_EXCEPTION, ""); + + 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. + * + * @param source the upstream + * + * @return a name + */ + static String resolveName(Publisher source) { + Scannable scannable = Scannable.from(source); + if (scannable.isScanAvailable()) { + String nameOrDefault = scannable.name(); + if (scannable.stepName() + .equals(nameOrDefault)) { + return REACTOR_DEFAULT_NAME; + } + else { + return nameOrDefault; + } + } + else { + log.warn("Attempting to activate metrics but the upstream is not Scannable. You might want to use `name()` (and optionally `tags()`) right before `metrics()`"); + return REACTOR_DEFAULT_NAME; + } + + } + + /** + * Extract the tags from the upstream + * + * @param source the upstream + * + * @return a {@link Tags} of {@link Tag} + */ + static Tags resolveTags(Publisher source, Tags tags) { + 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); + } + + return tags; + } + + /* + * This method calls the registry, which can be costly. However the cancel signal is only expected + * once per Subscriber. So the net effect should be that the registry is only called once, which + * is equivalent to registering the meter as a final field, with the added benefit of paying that + * cost only in case of cancellation. + */ + static void recordCancel(String name, Tags commonTags, MeterRegistry registry, Timer.Sample flowDuration) { + Timer timer = Timer.builder(name + METER_FLOW_DURATION) + .tags(commonTags.and(TAG_CANCEL)) + .description( + "Times the duration elapsed between a subscription and the cancellation of the sequence") + .register(registry); + + flowDuration.stop(timer); + } + + /* + * This method calls the registry, which can be costly. However a malformed signal is generally + * not expected, or at most once per Subscriber. So the net effect should be that the registry + * is only called once, which is equivalent to registering the meter as a final field, + * with the added benefit of paying that cost only in case of onNext/onError after termination. + */ + static void recordMalformed(String name, Tags commonTags, MeterRegistry registry) { + registry.counter(name + FluxMetrics.METER_MALFORMED, commonTags) + .increment(); + } + + /* + * This method calls the registry, which can be costly. However the onError signal is expected + * at most once per Subscriber. So the net effect should be that the registry is only called once, + * which is equivalent to registering the meter as a final field, with the added benefit of paying + * that cost only in case of error. + */ + static void recordOnError(String name, Tags commonTags, MeterRegistry registry, Timer.Sample flowDuration, Throwable e) { + Timer timer = Timer.builder(name + METER_FLOW_DURATION) + .tags(commonTags.and(TAG_ON_ERROR)) + .tag(TAG_KEY_EXCEPTION, + e.getClass() + .getName()) + .description( + "Times the duration elapsed between a subscription and the onError termination of the sequence, with the exception name as a tag.") + .register(registry); + + flowDuration.stop(timer); + } + + /* + * This method calls the registry, which can be costly. However the onComplete signal is expected + * at most once per Subscriber. So the net effect should be that the registry is only called once, + * which is equivalent to registering the meter as a final field, with the added benefit of paying + * that cost only in case of completion (which is not always occurring). + */ + static void recordOnComplete(String name, Tags commonTags, MeterRegistry registry, Timer.Sample flowDuration) { + Timer timer = Timer.builder(name + METER_FLOW_DURATION) + .tags(commonTags.and(TAG_ON_COMPLETE)) + .description( + "Times the duration elapsed between a subscription and the onComplete termination of a sequence that did emit some elements") + .register(registry); + + flowDuration.stop(timer); + } + + /* + * This method calls the registry, which can be costly. However the onComplete signal is expected + * at most once per Subscriber. So the net effect should be that the registry is only called once, + * which is equivalent to registering the meter as a final field, with the added benefit of paying + * that cost only in case of completion (which is not always occurring). + */ + static void recordOnCompleteEmpty(String name, Tags commonTags, MeterRegistry registry, Timer.Sample flowDuration) { + Timer timer = Timer.builder(name + METER_FLOW_DURATION) + .tags(commonTags.and(TAG_ON_COMPLETE_EMPTY)) + .description( + "Times the duration elapsed between a subscription and the onComplete termination of a sequence that didn't emit any element") + .register(registry); + + flowDuration.stop(timer); + } + + /* + * This method calls the registry, which can be costly. However the onSubscribe signal is expected + * at most once per Subscriber. So the net effect should be that the registry is only called once, + * which is equivalent to registering the meter as a final field, with the added benefit of paying + * that cost only in case of subscription. + */ + static void recordOnSubscribe(String name, Tags commonTags, MeterRegistry registry) { + Counter.builder(name + METER_SUBSCRIBED) + .tags(commonTags) + .description("Counts how many Reactor sequences have been subscribed to") + .register(registry) + .increment(); + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxMetricsFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxMetricsFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxMetricsFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2018-2021 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.TimeUnit; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Timer; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.Metrics; +import reactor.util.annotation.Nullable; + +import static reactor.core.publisher.FluxMetrics.*; + +/** + * Activate metrics gathering on a {@link Flux} (Fuseable version), assumes Micrometer is on the classpath. + + * @implNote Metrics.isInstrumentationAvailable() test should be performed BEFORE instantiating or referencing this + * class, otherwise a {@link NoClassDefFoundError} will be thrown if Micrometer is not there. + * + * @author Simon Baslé + * @author Stephane Maldini + */ +final class FluxMetricsFuseable extends InternalFluxOperator implements Fuseable { + + final String name; + final Tags tags; + final MeterRegistry registryCandidate; + + FluxMetricsFuseable(Flux flux) { + super(flux); + + this.name = resolveName(flux); + this.tags = resolveTags(flux, FluxMetrics.DEFAULT_TAGS_FLUX); + this.registryCandidate = Metrics.MicrometerConfiguration.getRegistry(); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new MetricsFuseableSubscriber<>(actual, registryCandidate, Clock.SYSTEM, this.name, this.tags); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + /** + * @param + * + * @implNote we don't want to particularly track fusion-specific calls, as this subscriber would only detect fused + * 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 + implements Fuseable, QueueSubscription { + + int mode; + + @Nullable + Fuseable.QueueSubscription qs; + + MetricsFuseableSubscriber(CoreSubscriber actual, + MeterRegistry registry, + Clock clock, + String sequenceName, + Tags sequenceTags) { + super(actual, registry, clock, sequenceName, sequenceTags); + } + + @Override + public void clear() { + if (qs != null) { + qs.clear(); + } + } + + @Override + public boolean isEmpty() { + return qs == null || qs.isEmpty(); + } + + @Override + public void onNext(T t) { + if (this.mode == Fuseable.ASYNC) { + actual.onNext(null); + return; + } + + if (done) { + recordMalformed(sequenceName, commonTags, registry); + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + //record the delay since previous onNext/onSubscribe. This also records the count. + long last = this.lastNextEventNanos; + this.lastNextEventNanos = clock.monotonicTime(); + this.onNextIntervalTimer.record(lastNextEventNanos - last, TimeUnit.NANOSECONDS); + + actual.onNext(t); + } + + @Override + @Nullable + public T poll() { + if (qs == null) { + return null; + } + try { + T v = qs.poll(); + + if (v == null && mode == SYNC) { + if (this.onNextIntervalTimer.count() == 0) { + recordOnCompleteEmpty(sequenceName, commonTags, registry, subscribeToTerminateSample); + } else { + recordOnComplete(sequenceName, commonTags, registry, subscribeToTerminateSample); + } + } + if (v != null) { + //this is an onNext event + //record the delay since previous onNext/onSubscribe. This also records the count. + long last = this.lastNextEventNanos; + this.lastNextEventNanos = clock.monotonicTime(); + this.onNextIntervalTimer.record(lastNextEventNanos - last, TimeUnit.NANOSECONDS); + } + return v; + } + catch (Throwable e) { + recordOnError(sequenceName, commonTags, registry, subscribeToTerminateSample, e); + throw e; + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + recordOnSubscribe(sequenceName, commonTags, registry); + this.subscribeToTerminateSample = Timer.start(clock); + this.lastNextEventNanos = clock.monotonicTime(); + this.qs = Operators.as(s); + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + public int requestFusion(int mode) { + //Simply negotiate the fusion by delegating: + if (qs != null) { + this.mode = qs.requestFusion(mode); + return this.mode; + } + return Fuseable.NONE; //should not happen unless requestFusion called before subscribe + } + + @Override + public int size() { + return qs == null ? 0 : qs.size(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxName.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxName.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxName.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2016-2021 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.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +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 + * {@link reactor.core.Scannable.Attr#TAGS TAGS} + * attribute. + * + * @author Simon Baslé + * @author Stephane Maldini + */ +final class FluxName extends InternalFluxOperator { + + final String name; + + final Set> tags; + + @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); + } + if (source instanceof FluxNameFuseable) { + FluxNameFuseable s = (FluxNameFuseable) source; + return new FluxNameFuseable<>(s.source, name, s.tags); + } + 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)); + + if (source instanceof FluxName) { + FluxName s = (FluxName) source; + if(s.tags != null) { + tags = new HashSet<>(tags); + tags.addAll(s.tags); + } + 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); + } + return new FluxNameFuseable<>(s.source, s.name, tags); + } + if (source instanceof Fuseable) { + return new FluxNameFuseable<>(source, null, tags); + } + return new FluxName<>(source, null, tags); + } + + FluxName(Flux source, + @Nullable String name, + @Nullable Set> tags) { + super(source); + this.name = name; + this.tags = tags; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.NAME) { + return name; + } + + if (key == Attr.TAGS && tags != null) { + return tags.stream(); + } + + if (key == RUN_STYLE) { + return SYNC; + } + + return super.scanUnsafe(key); + } + + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxNameFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxNameFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxNameFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016-2021 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.Set; + +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 + * {@link Attr#TAGS TAGS} + * attribute. + * + * @author Simon Baslé + * @author Stephane Maldini + */ +final class FluxNameFuseable extends InternalFluxOperator implements Fuseable { + + final String name; + + final Set> tags; + + FluxNameFuseable(Flux source, + @Nullable String name, + @Nullable Set> tags) { + super(source); + this.name = name; + this.tags = tags; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.NAME) { + return name; + } + + if (key == Attr.TAGS && tags != null) { + return tags.stream(); + } + + if (key == RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return super.scanUnsafe(key); + } + + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxNever.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxNever.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxNever.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016-2021 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.Publisher; +import reactor.core.CoreSubscriber; + +/** + * Represents a never publisher which only calls onSubscribe. + *

+ * This Publisher is effectively stateless and only a single instance exists. + * Use the {@link #instance()} method to obtain a properly type-parametrized view of it. + * + * @see Reactive-Streams-Commons + */ +final class FluxNever extends Flux implements SourceProducer { + + static final Publisher INSTANCE = new FluxNever(); + + FluxNever() { + // deliberately no op + } + + @Override + public void subscribe(CoreSubscriber actual) { + actual.onSubscribe(Operators.emptySubscription()); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + /** + * Returns a properly parametrized instance of this never Publisher. + * + * @param the value type + * @return a properly parametrized instance of this never Publisher + */ + @SuppressWarnings("unchecked") + static Flux instance() { + return (Flux) INSTANCE; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxOnAssembly.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxOnAssembly.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxOnAssembly.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,690 @@ +/* + * Copyright (c) 2016-2021 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.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +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.function.Tuple3; +import reactor.util.function.Tuples; + +/** + * Captures the current stacktrace when this publisher is created and + * makes it available/visible for debugging purposes from + * the inner Subscriber. + *

+ * Note that getting a stacktrace is a costly operation. + *

+ * The operator sanitizes the stacktrace and removes noisy entries such as: + *

    + *
  • java.lang.Thread entries
  • + *
  • method references with source line of 1 (bridge methods)
  • + *
  • Tomcat worker thread entries
  • + *
  • JUnit setup
  • + *
+ * + * @param the value type passing through + * @see https://github.com/reactor/reactive-streams-commons + */ +final class FluxOnAssembly extends InternalFluxOperator implements Fuseable, + AssemblyOp { + + final AssemblySnapshot snapshotStack; + + /** + * Create an assembly trace decorated as a {@link Flux}. + */ + FluxOnAssembly(Flux source, AssemblySnapshot snapshotStack) { + super(source); + this.snapshotStack = snapshotStack; + } + + @Override + public String stepName() { + return snapshotStack.operatorAssemblyInformation(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL_METADATA) return !snapshotStack.isCheckpoint; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public String toString() { + return snapshotStack.operatorAssemblyInformation(); + } + + static void fillStacktraceHeader(StringBuilder sb, Class sourceClass, @Nullable String description) { + sb.append("\nAssembly trace from producer [") + .append(sourceClass.getName()) + .append("]"); + + if (description != null) { + sb.append(", described as [") + .append(description) + .append("]"); + } + sb.append(" :\n"); + } + + @SuppressWarnings("unchecked") + static CoreSubscriber wrapSubscriber(CoreSubscriber actual, + Flux source, + Publisher current, + @Nullable AssemblySnapshot snapshotStack) { + if(snapshotStack != null) { + if (actual instanceof ConditionalSubscriber) { + ConditionalSubscriber cs = (ConditionalSubscriber) actual; + return new OnAssemblyConditionalSubscriber<>(cs, snapshotStack, source, current); + } + else { + return new OnAssemblySubscriber<>(actual, snapshotStack, source, current); + } + } + else { + return actual; + } + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return wrapSubscriber(actual, source, this, snapshotStack); + } + + /** + * A snapshot of current assembly traceback, possibly with a user-readable + * description or a wider correlation ID. + */ + static class AssemblySnapshot { + + final boolean isCheckpoint; + @Nullable + final String description; + @Nullable + final Supplier assemblyInformationSupplier; + String cached; + + /** + * @param description a description for the assembly traceback. + * @param assemblyInformationSupplier a callSite supplier. + */ + AssemblySnapshot(@Nullable String description, Supplier assemblyInformationSupplier) { + this(description != null, description, assemblyInformationSupplier); + } + + AssemblySnapshot(String assemblyInformation) { + this.isCheckpoint = false; + this.description = null; + this.assemblyInformationSupplier = null; + this.cached = assemblyInformation; + } + + private AssemblySnapshot(boolean isCheckpoint, @Nullable String description, + @Nullable Supplier assemblyInformationSupplier) { + this.isCheckpoint = isCheckpoint; + this.description = description; + this.assemblyInformationSupplier = assemblyInformationSupplier; + } + + public boolean hasDescription() { + return this.description != null; + } + + @Nullable + public String getDescription() { + return description; + } + + public boolean isCheckpoint() { + return this.isCheckpoint; + } + + public boolean isLight() { + return false; + } + + public String lightPrefix() { + return ""; + } + + String toAssemblyInformation() { + if(cached == null) { + if (assemblyInformationSupplier == null) { + throw new IllegalStateException("assemblyInformation must either be supplied or resolvable"); + } + cached = assemblyInformationSupplier.get(); + } + return cached; + } + + String operatorAssemblyInformation() { + return Traces.extractOperatorAssemblyInformation(toAssemblyInformation()); + } + } + + static final class CheckpointLightSnapshot extends AssemblySnapshot { + + CheckpointLightSnapshot(@Nullable String description) { + super(true, description, null); + this.cached = "checkpoint(\"" + (description == null ? "" : description) + "\")"; + } + + @Override + public boolean isLight() { + return true; + } + + @Override + public String lightPrefix() { + return "checkpoint"; + } + + @Override + String operatorAssemblyInformation() { + return cached; + } + + } + + static final class CheckpointHeavySnapshot extends AssemblySnapshot { + + CheckpointHeavySnapshot(@Nullable String description, Supplier assemblyInformationSupplier) { + super(true, description, assemblyInformationSupplier); + } + + /** + * The lightPrefix is used despite the exception not being a light one (it will have a callsite supplier). + * @return the heavy checkpoint prefix, with description if relevant (eg. "checkpoint(heavy)") + */ + @Override + public String lightPrefix() { + return "checkpoint(" + (description == null ? "" : description) + ")"; + } + } + + static final class MethodReturnSnapshot extends AssemblySnapshot { + + MethodReturnSnapshot(String method) { + super(false, method, null); + cached = method; + } + + @Override + public boolean isLight() { + return true; + } + + @Override + String operatorAssemblyInformation() { + return cached; + } + } + + static final class ObservedAtInformationNode implements Serializable { + private static final long serialVersionUID = 1L; + + final int id; + final String operator; + final String message; + + int occurrenceCounter; + + @Nullable + ObservedAtInformationNode parent; + Set children; + + ObservedAtInformationNode(int id, String operator, String message) { + this.id = id; + this.operator = operator; + this.message = message; + this.occurrenceCounter = 0; + this.children = new LinkedHashSet<>(); + } + + void incrementCount() { + this.occurrenceCounter++; + } + + void addNode(ObservedAtInformationNode node) { + if (this == node) { + return; + } + if (children.add(node)) { + node.parent = this; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ObservedAtInformationNode node = (ObservedAtInformationNode) o; + return id == node.id && operator.equals(node.operator) && message.equals(node.message); + } + + @Override + public int hashCode() { + return Objects.hash(id, operator, message); + } + + @Override + public String toString() { + return operator + "{" + + "@" + id + + (children.isEmpty() ? "" : ", " + children.size() + " children") + + '}'; + } + } + + /** + * The holder for the assembly stacktrace (as its message). + * + * @implNote this package-private exception needs access to package-private enclosing class and methods, + * but it is also detected by {@link Exceptions#isTraceback(Throwable)} via {@link Class#getCanonicalName() reflection}. + * Be sure to update said method in case of a refactoring. + * + * @see Exceptions#isTraceback(Throwable) + */ + static final class OnAssemblyException extends RuntimeException { + + /** */ + private static final long serialVersionUID = -6342981676020433721L; + + final Map nodesPerId = new HashMap<>(); + final ObservedAtInformationNode root = new ObservedAtInformationNode(-1, "ROOT", "ROOT"); + int maxOperatorSize = 0; + + OnAssemblyException(String message) { + super(message); + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + + void add(Publisher parent, Publisher current, AssemblySnapshot snapshot) { + if (snapshot.isCheckpoint()) { + if (snapshot.isLight()) { + add(parent, current, snapshot.lightPrefix(), Objects.requireNonNull(snapshot.getDescription())); + } + else { + String assemblyInformation = snapshot.toAssemblyInformation(); + String[] parts = Traces.extractOperatorAssemblyInformationParts(assemblyInformation); + + if (parts.length > 0) { + //we ignore the first part if there are two (classname and callsite). only use the line part + String line = parts[parts.length - 1]; + add(parent, current, snapshot.lightPrefix(), line); + } + else { + //should not happen with heavy checkpoints + add(parent, current, snapshot.lightPrefix(), Objects.requireNonNull(snapshot.getDescription())); + } + } + } + else { + String assemblyInformation = snapshot.toAssemblyInformation(); + String[] parts = Traces.extractOperatorAssemblyInformationParts(assemblyInformation); + if (parts.length > 0) { + String prefix = parts.length > 1 ? parts[0] : ""; + String line = parts[parts.length - 1]; + + add(parent, current, prefix, line); + } + } + } + + private void add(Publisher operator, Publisher currentAssembly, String prefix, String line) { + Scannable parentAssembly = Scannable.from(currentAssembly) + .parents() + .filter(s -> s instanceof AssemblyOp) + .findFirst() + .orElse(null); + + int thisId = System.identityHashCode(currentAssembly); + int parentId = System.identityHashCode(parentAssembly); + + ObservedAtInformationNode thisNode; + synchronized (nodesPerId) { + thisNode = nodesPerId.get(thisId); + if (thisNode != null) { + thisNode.incrementCount(); + } + else { + thisNode = new ObservedAtInformationNode(thisId, prefix, line); + nodesPerId.put(thisId, thisNode); + } + + if (parentAssembly == null) { + root.addNode(thisNode); + } + else { + ObservedAtInformationNode parentNode = nodesPerId.get(parentId); + if (parentNode != null) { + parentNode.addNode(thisNode); + } + else { + root.addNode(thisNode); + } + } + + //pre-compute the maximum width of the operators + int length = thisNode.operator.length(); + if (length > this.maxOperatorSize) { + this.maxOperatorSize = length; + } + } + } + + void findPathToLeaves(ObservedAtInformationNode node, List> rootPaths) { + if (node.children.isEmpty()) { + List pathForLeaf = new LinkedList<>(); + ObservedAtInformationNode traversed = node; + while (traversed != null && traversed != root) { + pathForLeaf.add(0, traversed); + traversed = traversed.parent; + } + rootPaths.add(pathForLeaf); + return; + } + node.children.forEach(n -> findPathToLeaves(n, rootPaths)); + } + + @Override + public String getMessage() { + //skip the "error has been observed" traceback if mapped traceback is empty + synchronized (nodesPerId) { + if (root.children.isEmpty()) { + return super.getMessage(); + } + + StringBuilder sb = new StringBuilder(super.getMessage()) + .append(System.lineSeparator()) + .append("Error has been observed at the following site(s):") + .append(System.lineSeparator()); + + List> rootPaths = new ArrayList<>(); + root.children.forEach(actualRoot -> findPathToLeaves(actualRoot, rootPaths)); + + rootPaths.forEach(path -> path.forEach(node -> { + boolean isRoot = node.parent == null || node.parent == root; + sb.append("\t"); + String connector = "|_"; + if (isRoot) { + connector = "*_"; + } + sb.append(connector); + + char filler = isRoot ? '_' : ' '; + for (int i = node.operator.length(); i < this.maxOperatorSize; i++) { + sb.append(filler); + } + sb.append(filler); + sb.append(node.operator); + sb.append(Traces.CALL_SITE_GLUE); + sb.append(node.message); + if (node.occurrenceCounter > 0) { + sb.append(" (observed ").append(node.occurrenceCounter + 1).append(" times)"); + } + sb.append(System.lineSeparator()); + })); + + sb.append("Original Stack Trace:"); + return sb.toString(); + } + } + + @Override + public String toString() { + String message = getLocalizedMessage(); + if (message == null) { + return "The stacktrace should have been enhanced by Reactor, but there was no message in OnAssemblyException"; + } + return "The stacktrace has been enhanced by Reactor, refer to additional information below: " + message; + } + } + + static class OnAssemblySubscriber + implements InnerOperator, QueueSubscription { + + final AssemblySnapshot snapshotStack; + final Publisher parent; + final Publisher current; + final CoreSubscriber actual; + + QueueSubscription qs; + Subscription s; + int fusionMode; + + OnAssemblySubscriber(CoreSubscriber actual, + AssemblySnapshot snapshotStack, + Publisher parent, + Publisher current) { + this.actual = actual; + this.snapshotStack = snapshotStack; + this.parent = parent; + this.current = current; + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL_METADATA) return !snapshotStack.isCheckpoint; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public String toString() { + return snapshotStack.operatorAssemblyInformation(); + } + + @Override + public String stepName() { + return toString(); + } + + @Override + final public void onNext(T t) { + actual.onNext(t); + } + + @Override + final public void onError(Throwable t) { + actual.onError(fail(t)); + } + + @Override + final public void onComplete() { + actual.onComplete(); + } + + @Override + final public int requestFusion(int requestedMode) { + QueueSubscription qs = this.qs; + if (qs != null) { + int m = qs.requestFusion(requestedMode); + if (m != Fuseable.NONE) { + fusionMode = m; + } + return m; + } + return Fuseable.NONE; + } + + final Throwable fail(Throwable t) { + boolean lightCheckpoint = snapshotStack.isLight(); + + OnAssemblyException onAssemblyException = null; + for (Throwable e : t.getSuppressed()) { + if (e instanceof OnAssemblyException) { + onAssemblyException = (OnAssemblyException) e; + break; + } + } + + if (onAssemblyException == null) { + if (lightCheckpoint) { + onAssemblyException = new OnAssemblyException(""); + } + else { + StringBuilder sb = new StringBuilder(); + fillStacktraceHeader(sb, parent.getClass(), snapshotStack.getDescription()); + sb.append(snapshotStack.toAssemblyInformation().replaceFirst("\\n$", "")); + String description = sb.toString(); + onAssemblyException = new OnAssemblyException(description); + } + + t = Exceptions.addSuppressed(t, onAssemblyException); + final StackTraceElement[] stackTrace = t.getStackTrace(); + if (stackTrace.length > 0) { + StackTraceElement[] newStackTrace = new StackTraceElement[stackTrace.length]; + int i = 0; + for (StackTraceElement stackTraceElement : stackTrace) { + String className = stackTraceElement.getClassName(); + + if (className.startsWith("reactor.core.publisher.") && className.contains("OnAssembly")) { + continue; + } + + newStackTrace[i] = stackTraceElement; + i++; + } + newStackTrace = Arrays.copyOf(newStackTrace, i); + + onAssemblyException.setStackTrace(newStackTrace); + t.setStackTrace(new StackTraceElement[] { + stackTrace[0] + }); + } + } + + onAssemblyException.add(parent, current, snapshotStack); + + return t; + } + + @Override + final public boolean isEmpty() { + try { + return qs.isEmpty(); + } + catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw Exceptions.propagate(fail(ex)); + } + } + + @Override + final public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + this.qs = Operators.as(s); + actual.onSubscribe(this); + } + } + + @Override + final public int size() { + return qs.size(); + } + + @Override + final public void clear() { + qs.clear(); + } + + @Override + final public void request(long n) { + s.request(n); + } + + @Override + final public void cancel() { + s.cancel(); + } + + @Override + @Nullable + final public T poll() { + try { + return qs.poll(); + } + catch (final Throwable ex) { + Exceptions.throwIfFatal(ex); + throw Exceptions.propagate(fail(ex)); + } + } + } + + static final class OnAssemblyConditionalSubscriber extends OnAssemblySubscriber + implements ConditionalSubscriber { + + final ConditionalSubscriber actualCS; + + OnAssemblyConditionalSubscriber(ConditionalSubscriber actual, + AssemblySnapshot stacktrace, Publisher parent, Publisher current) { + super(actual, stacktrace, parent, current); + this.actualCS = actual; + } + + @Override + public boolean tryOnNext(T t) { + return actualCS.tryOnNext(t); + } + + } + +} + +interface AssemblyOp {} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureBuffer.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureBuffer.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureBuffer.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2015-2021 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.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Consumer; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * @author Stephane Maldini + */ +final class FluxOnBackpressureBuffer extends InternalFluxOperator implements Fuseable { + + final Consumer onOverflow; + final int bufferSize; + final boolean unbounded; + + FluxOnBackpressureBuffer(Flux source, + int bufferSize, + boolean unbounded, + @Nullable Consumer onOverflow) { + super(source); + if (bufferSize < 1) { + throw new IllegalArgumentException("Buffer Size must be strictly positive"); + } + this.bufferSize = bufferSize; + this.unbounded = unbounded; + this.onOverflow = onOverflow; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new BackpressureBufferSubscriber<>(actual, + bufferSize, + unbounded, + onOverflow); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + static final class BackpressureBufferSubscriber + implements QueueSubscription, InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + final Queue queue; + final int capacityOrSkip; + final Consumer onOverflow; + + Subscription s; + + volatile boolean cancelled; + + volatile boolean enabledFusion; + + volatile boolean done; + Throwable error; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(BackpressureBufferSubscriber.class, + "wip"); + + volatile int discardGuard; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater DISCARD_GUARD = + AtomicIntegerFieldUpdater.newUpdater(BackpressureBufferSubscriber.class, + "discardGuard"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(BackpressureBufferSubscriber.class, + "requested"); + + BackpressureBufferSubscriber(CoreSubscriber actual, + int bufferSize, + boolean unbounded, + @Nullable Consumer onOverflow) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.onOverflow = onOverflow; + + Queue q; + + if (unbounded) { + q = Queues.unbounded(bufferSize).get(); + } + else { + q = Queues.get(bufferSize).get(); + } + + if (!unbounded && Queues.capacity(q) > bufferSize) { + this.capacityOrSkip = bufferSize; + } + else { + //for unbounded, the bufferSize is not terribly relevant + //for bounded, if the queue has exact capacity then when checking q.size() == capacityOrSkip, this will skip the check + this.capacityOrSkip = Integer.MAX_VALUE; + } + + this.queue = q; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.TERMINATED) return done && queue.isEmpty(); + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.BUFFERED) return queue.size(); + if (key == Attr.ERROR) return error; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.DELAY_ERROR) return true; + if (key == Attr.CAPACITY) return capacityOrSkip == Integer.MAX_VALUE ? Queues.capacity(queue) : capacityOrSkip; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + 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); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, ctx); + return; + } + if (cancelled) { + Operators.onDiscard(t, ctx); + } + + if ((capacityOrSkip != Integer.MAX_VALUE && queue.size() >= capacityOrSkip) || !queue.offer(t)) { + Throwable ex = Operators.onOperatorError(s, Exceptions.failWithOverflow(), t, ctx); + if (onOverflow != null) { + try { + onOverflow.accept(t); + } + catch (Throwable e) { + Exceptions.throwIfFatal(e); + ex.initCause(e); + } + } + Operators.onDiscard(t, ctx); + onError(ex); + return; + } + drain(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, ctx); + return; + } + error = t; + done = true; + drain(null); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + drain(null); + } + + void drain(@Nullable T dataSignal) { + if (WIP.getAndIncrement(this) != 0) { + if (dataSignal != null && cancelled) { + Operators.onDiscard(dataSignal, actual.currentContext()); + } + return; + } + + int missed = 1; + + for (; ; ) { + Subscriber a = actual; + if (a != null) { + + if (enabledFusion) { + drainFused(a); + } + else { + drainRegular(a); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drainRegular(Subscriber 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, t)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + e++; + } + + if (r == e) { + if (checkTerminated(done, q.isEmpty(), a, null)) { + return; + } + } + + if (e != 0 && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drainFused(Subscriber a) { + int missed = 1; + + final Queue q = queue; + + 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(); + return; + } + + boolean d = done; + + a.onNext(null); + + if (d) { + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(null); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + s.cancel(); + + if (WIP.getAndIncrement(this) == 0) { + if (!enabledFusion) { + // 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, ctx, null); + } + } + } + } + + @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, ctx, 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) { + enabledFusion = true; + return Fuseable.ASYNC; + } + return Fuseable.NONE; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, @Nullable T v) { + if (cancelled) { + s.cancel(); + Operators.onDiscard(v, ctx); + Operators.onDiscardQueueWithClear(queue, ctx, null); + return true; + } + if (d) { + //the operator always delays the errors, particularly overflow one + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } + else { + a.onComplete(); + } + return true; + } + } + return false; + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureBufferStrategy.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureBufferStrategy.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureBufferStrategy.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2015-2021 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.ArrayDeque; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Consumer; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Buffers values if the subscriber doesn't request fast enough, bounding the + * buffer to a chosen size. If the buffer overflows, apply a pre-determined + * overflow strategy. + + * @author Stephane Maldini + * @author Simon Baslé + */ +final class FluxOnBackpressureBufferStrategy extends InternalFluxOperator { + + final Consumer onBufferOverflow; + final int bufferSize; + final boolean delayError; + final BufferOverflowStrategy bufferOverflowStrategy; + + FluxOnBackpressureBufferStrategy(Flux source, + int bufferSize, + @Nullable Consumer onBufferOverflow, + BufferOverflowStrategy bufferOverflowStrategy) { + super(source); + if (bufferSize < 1) { + throw new IllegalArgumentException("Buffer Size must be strictly positive"); + } + + this.bufferSize = bufferSize; + this.onBufferOverflow = onBufferOverflow; + this.bufferOverflowStrategy = bufferOverflowStrategy; + this.delayError = onBufferOverflow != null || bufferOverflowStrategy == BufferOverflowStrategy.ERROR; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new BackpressureBufferDropOldestSubscriber<>(actual, + bufferSize, + delayError, onBufferOverflow, bufferOverflowStrategy); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class BackpressureBufferDropOldestSubscriber + extends ArrayDeque + implements InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + final int bufferSize; + final Consumer onOverflow; + final boolean delayError; + final BufferOverflowStrategy overflowStrategy; + + Subscription s; + + volatile boolean cancelled; + + volatile boolean done; + Throwable error; + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(BackpressureBufferDropOldestSubscriber.class, + "wip"); + + volatile long requested; + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(BackpressureBufferDropOldestSubscriber.class, + "requested"); + + BackpressureBufferDropOldestSubscriber( + CoreSubscriber actual, + int bufferSize, + boolean delayError, + @Nullable Consumer onOverflow, + BufferOverflowStrategy overflowStrategy) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.delayError = delayError; + this.onOverflow = onOverflow; + this.overflowStrategy = overflowStrategy; + this.bufferSize = bufferSize; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.TERMINATED) return done && isEmpty(); + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.BUFFERED) return size(); + if (key == Attr.ERROR) return error; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + 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); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, ctx); + return; + } + + boolean callOnOverflow = false; + boolean callOnError = false; + T overflowElement = t; + synchronized(this) { + if (size() == bufferSize) { + callOnOverflow = true; + switch (overflowStrategy) { + case DROP_OLDEST: + overflowElement = pollFirst(); + offer(t); + break; + case DROP_LATEST: + //do nothing + break; + case ERROR: + default: + callOnError = true; + break; + } + } + else { + offer(t); + } + } + + if (callOnOverflow) { + if (onOverflow != null) { + try { + onOverflow.accept(overflowElement); + } + catch (Throwable e) { + Throwable ex = Operators.onOperatorError(s, e, overflowElement, ctx); + onError(ex); + return; + } + finally { + Operators.onDiscard(overflowElement, ctx); + } + } + else { + Operators.onDiscard(overflowElement, ctx); + } + } + + if (callOnError) { + Throwable ex = Operators.onOperatorError(s, Exceptions.failWithOverflow(), overflowElement, ctx); + onError(ex); + } + + if (!callOnError && !callOnOverflow) { + drain(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, ctx); + return; + } + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + drain(); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + + for (; ; ) { + Subscriber a = actual; + //noinspection ConstantConditions + if (a != null) { + innerDrain(a); + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void innerDrain(Subscriber a) { + int missed = 1; + + for (; ; ) { + + long r = requested; + long e = 0L; + + while (r != e) { + boolean d = done; + + T t; + synchronized (this) { + t = poll(); + } + boolean empty = t == null; + + if (checkTerminated(d, empty, a)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + e++; + } + + if (r == e) { + boolean empty; + synchronized (this) { + empty = isEmpty(); + } + if (checkTerminated(done, empty, a)) { + return; + } + } + + if (e != 0L && r != Long.MAX_VALUE) { + Operators.produced(REQUESTED, this, e); + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + s.cancel(); + + if (WIP.getAndIncrement(this) == 0) { + synchronized (this) { + clear(); + } + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a) { + if (cancelled) { + s.cancel(); + synchronized (this) { + clear(); + } + return true; + } + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } + else { + a.onComplete(); + } + return true; + } + } + else { + Throwable e = error; + if (e != null) { + synchronized (this) { + clear(); + } + a.onError(e); + return true; + } + else if (empty) { + a.onComplete(); + return true; + } + } + } + return false; + } + + @Override + public void clear() { + Operators.onDiscardMultiple(this, ctx); + super.clear(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureBufferTimeout.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureBufferTimeout.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureBufferTimeout.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,410 @@ +/* + * Copyright (c) 2017-2021 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.ArrayDeque; +import java.util.Objects; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Consumer; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.scheduler.Scheduler; +import reactor.util.Logger; +import reactor.util.Loggers; +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.ASYNC; + +/** + * Buffers values if the subscriber doesn't request fast enough, bounding the buffer to a + * chosen size and applying a TTL (time-to-live) to the elements. If the buffer overflows, + * drop the oldest element. + * + * @author Stephane Maldini + * @author Simon Baslé + * @author David Karnok + */ +//see https://github.com/akarnokd/RxJava2Extensions/blob/master/src/main/java/hu/akarnokd/rxjava2/operators/FlowableOnBackpressureTimeout.java +final class FluxOnBackpressureBufferTimeout extends InternalFluxOperator { + + private static final Logger LOGGER = + Loggers.getLogger(FluxOnBackpressureBufferTimeout.class); + + final Duration ttl; + final Scheduler ttlScheduler; + final int bufferSize; + final Consumer onBufferEviction; + + FluxOnBackpressureBufferTimeout(Flux source, + Duration ttl, + Scheduler ttlScheduler, + int bufferSize, + Consumer onBufferEviction) { + super(source); + this.ttl = ttl; + this.ttlScheduler = ttlScheduler; + this.bufferSize = bufferSize; + this.onBufferEviction = onBufferEviction; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new BackpressureBufferTimeoutSubscriber<>(actual, + ttl, + ttlScheduler, + bufferSize, + onBufferEviction); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return ttlScheduler; + if (key == RUN_STYLE) return ASYNC; + + return super.scanUnsafe(key); + } + + static final class BackpressureBufferTimeoutSubscriber extends ArrayDeque + implements InnerOperator, Runnable { + + final CoreSubscriber actual; + final Context ctx; + final Duration ttl; + final Scheduler ttlScheduler; + final Scheduler.Worker worker; + final int bufferSizeDouble; + final Consumer onBufferEviction; + + Subscription s; + + volatile boolean cancelled; + + volatile boolean done; + Throwable error; + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(BackpressureBufferTimeoutSubscriber.class, + "wip"); + + volatile long requested; + static final AtomicLongFieldUpdater + REQUESTED = AtomicLongFieldUpdater.newUpdater( + BackpressureBufferTimeoutSubscriber.class, + "requested"); + + BackpressureBufferTimeoutSubscriber(CoreSubscriber actual, + Duration ttl, + Scheduler ttlScheduler, + int bufferSize, + Consumer onBufferEviction) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.onBufferEviction = Objects.requireNonNull(onBufferEviction, + "buffer eviction callback must not be null"); + this.bufferSizeDouble = bufferSize << 1; + this.ttl = ttl; + this.ttlScheduler = Objects.requireNonNull(ttlScheduler, + "ttl Scheduler must not be null"); + this.worker = ttlScheduler.createWorker(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) { + return requested; + } + if (key == Attr.TERMINATED) { + return done && isEmpty(); + } + if (key == Attr.CANCELLED) { + return cancelled; + } + if (key == Attr.BUFFERED) { + return size(); + } + if (key == Attr.ERROR) { + return error; + } + if (key == Attr.PREFETCH) { + return Integer.MAX_VALUE; + } + if (key == Attr.DELAY_ERROR) { + return false; + } + if (key == Attr.RUN_ON) { + return ttlScheduler; + } + if (key == RUN_STYLE) { + return ASYNC; + } + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + cancelled = true; + s.cancel(); + worker.dispose(); + + if (WIP.getAndIncrement(this) == 0) { + clearQueue(); + } + } + + @SuppressWarnings("unchecked") + void clearQueue() { + for (; ; ) { + T evicted; + synchronized (this) { + if (this.isEmpty()) { + break; + } + + this.poll(); + evicted = (T) this.poll(); + } + + evict(evicted); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onNext(T t) { + T evicted = null; + synchronized (this) { + if (this.size() == bufferSizeDouble) { + this.poll(); + evicted = (T) this.poll(); + } + this.offer(ttlScheduler.now(TimeUnit.NANOSECONDS)); + this.offer(t); + } + evict(evicted); + try { + worker.schedule(this, ttl.toNanos(), TimeUnit.NANOSECONDS); + } + catch (RejectedExecutionException re) { + done = true; + error = Operators.onRejectedExecution(re, this, null, t, + actual.currentContext()); + } + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + @SuppressWarnings("unchecked") + @Override + public void run() { + for (; ; ) { + if (cancelled) { + break; + } + + boolean d = done; + boolean empty; + T evicted = null; + + synchronized (this) { + Long ts = (Long) this.peek(); + empty = ts == null; + if (!empty) { + if (ts <= ttlScheduler.now(TimeUnit.NANOSECONDS) - ttl.toNanos()) { + this.poll(); + evicted = (T) this.poll(); + } + else { + break; + } + } + } + + evict(evicted); + + if (empty) { + if (d) { + drain(); + } + break; + } + } + } + + void evict(@Nullable T evicted) { + if (evicted != null) { + try { + onBufferEviction.accept(evicted); + } + catch (Throwable ex) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "value [{}] couldn't be evicted due to a callback error. This error will be dropped: {}", + evicted, + ex); + } + Operators.onErrorDropped(ex, actual.currentContext()); + } + Operators.onDiscard(evicted, actual.currentContext()); + } + } + + @SuppressWarnings("unchecked") + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + + for (; ; ) { + long r = requested; + long e = 0; + + while (e != r) { + if (cancelled) { + clearQueue(); + return; + } + + boolean d = done; + T v; + + synchronized (this) { + if (this.poll() != null) { + v = (T) this.poll(); + } + else { + v = null; + } + } + + boolean empty = v == null; + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + actual.onError(ex); + } + else { + actual.onComplete(); + } + + worker.dispose(); + return; + } + + if (empty) { + break; + } + + actual.onNext(v); + + e++; + } + + if (e == r) { + if (cancelled) { + clearQueue(); + return; + } + + boolean d = done; + boolean empty; + synchronized (this) { + empty = this.isEmpty(); + } + + if (d && empty) { + Throwable ex = error; + if (ex != null) { + actual.onError(ex); + } + else { + actual.onComplete(); + } + + worker.dispose(); + return; + } + } + + if (e != 0 && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureDrop.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureDrop.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureDrop.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2016-2021 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.function.Consumer; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Drops values if the subscriber doesn't request fast enough. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxOnBackpressureDrop extends InternalFluxOperator { + + static final Consumer NOOP = t -> { + }; + + final Consumer onDrop; + + FluxOnBackpressureDrop(Flux source) { + super(source); + this.onDrop = NOOP; + } + + FluxOnBackpressureDrop(Flux source, + Consumer onDrop) { + super(source); + this.onDrop = Objects.requireNonNull(onDrop, "onDrop"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new DropSubscriber<>(actual, onDrop); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class DropSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + final Consumer onDrop; + + Subscription s; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(DropSubscriber.class, "requested"); + + boolean done; + + DropSubscriber(CoreSubscriber actual, Consumer onDrop) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.onDrop = onDrop; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + s.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) { + try { + onDrop.accept(t); + } + catch (Throwable e) { + Operators.onErrorDropped(e, ctx); + } + Operators.onDiscard(t, ctx); + return; + } + + long r = requested; + if (r != 0L) { + actual.onNext(t); + if(r != Long.MAX_VALUE) { + Operators.produced(REQUESTED, this, 1); + } + } + else { + try { + onDrop.accept(t); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + } + Operators.onDiscard(t, ctx); + } + } + + @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; + + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.TERMINATED) return done; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureLatest.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureLatest.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxOnBackpressureLatest.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Runs the source in unbounded mode and emits only the latest value + * if the subscriber can't keep up properly. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxOnBackpressureLatest extends InternalFluxOperator { + + FluxOnBackpressureLatest(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new LatestSubscriber<>(actual); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class LatestSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(LatestSubscriber.class, "requested"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(LatestSubscriber.class, "wip"); + + Subscription s; + + Throwable error; + volatile boolean done; + + volatile boolean cancelled; + + volatile T value; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater VALUE = + AtomicReferenceFieldUpdater.newUpdater(LatestSubscriber.class, Object.class, "value"); + + LatestSubscriber(CoreSubscriber actual) { + this.actual = actual; + this.ctx = actual.currentContext(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + + cancelled = true; + + s.cancel(); + + if (WIP.getAndIncrement(this) == 0) { + Object toDiscard = VALUE.getAndSet(this, null); + if (toDiscard != null) { + Operators.onDiscard(toDiscard, ctx); + } + } + } + } + + @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) { + Object toDiscard = VALUE.getAndSet(this, t); + if (toDiscard != null) { + Operators.onDiscard(toDiscard, ctx); + } + drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + final Subscriber a = actual; + + int missed = 1; + + for (; ; ) { + + if (checkTerminated(done, value == null, a)) { + return; + } + + long r = requested; + long e = 0L; + + while (r != e) { + boolean d = done; + + @SuppressWarnings("unchecked") + T v = (T) VALUE.getAndSet(this, null); + + boolean empty = v == null; + + if (checkTerminated(d, empty, a)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (r == e && checkTerminated(done, value == null, a)) { + return; + } + + if (e != 0L && r != Long.MAX_VALUE) { + Operators.produced(REQUESTED, this, e); + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a) { + if (cancelled) { + Object toDiscard = VALUE.getAndSet(this, null); + if (toDiscard != null) { + Operators.onDiscard(toDiscard, ctx); + }return true; + } + + if (d) { + Throwable e = error; + if (e != null) { + Object toDiscard = VALUE.getAndSet(this, null); + if (toDiscard != null) { + Operators.onDiscard(toDiscard, ctx); + } + + a.onError(e); + return true; + } else if (empty) { + a.onComplete(); + return true; + } + } + + return false; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.BUFFERED) return value != null ? 1 : 0; + if (key == Attr.ERROR) return error; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxOnErrorResume.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxOnErrorResume.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxOnErrorResume.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; + +/** + * Resumes the failed main sequence with another sequence returned by + * a function for the particular failure exception. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxOnErrorResume extends InternalFluxOperator { + + final Function> nextFactory; + + FluxOnErrorResume(Flux source, + Function> nextFactory) { + super(source); + this.nextFactory = Objects.requireNonNull(nextFactory, "nextFactory"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new ResumeSubscriber<>(actual, nextFactory); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ResumeSubscriber + extends Operators.MultiSubscriptionSubscriber { + + final Function> nextFactory; + + boolean second; + + ResumeSubscriber(CoreSubscriber actual, + Function> nextFactory) { + super(actual); + this.nextFactory = nextFactory; + } + + @Override + public void onSubscribe(Subscription s) { + if (!second) { + actual.onSubscribe(this); + } + set(s); + } + + @Override + public void onNext(T t) { + actual.onNext(t); + + if (!second) { + producedOne(); + } + } + + @Override + public void onError(Throwable t) { + if (!second) { + second = true; + + Publisher p; + + try { + p = Objects.requireNonNull(nextFactory.apply(t), + "The nextFactory returned a null Publisher"); + } + catch (Throwable e) { + Throwable _e = Operators.onOperatorError(e, actual.currentContext()); + _e = Exceptions.addSuppressed(_e, t); + actual.onError(_e); + return; + } + p.subscribe(this); + } + else { + actual.onError(t); + } + } + + @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/FluxOperator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxOperator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxOperator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-2021 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.BiFunction; +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * A decorating {@link Flux} {@link Publisher} that exposes {@link Flux} API over an + * arbitrary {@link Publisher}. Useful to create operators which return a {@link Flux}. + * + * @param delegate {@link Publisher} type + * @param produced type + */ +public abstract class FluxOperator extends Flux implements Scannable { + + protected final Flux source; + + /** + * Build a {@link FluxOperator} wrapper around the passed parent {@link Publisher} + * + * @param source the {@link Publisher} to decorate + */ + protected FluxOperator(Flux source) { + this.source = Objects.requireNonNull(source); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + return null; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxPeek.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxPeek.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxPeek.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.core.publisher.FluxPeekFuseable.PeekConditionalSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Peek into the lifecycle events and signals of a sequence. + *

+ *

+ * The callbacks are all optional. + *

+ *

+ * Crashes by the lambdas are ignored. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxPeek extends InternalFluxOperator implements SignalPeek { + + final Consumer onSubscribeCall; + + final Consumer onNextCall; + + final Consumer onErrorCall; + + final Runnable onCompleteCall; + + final Runnable onAfterTerminateCall; + + final LongConsumer onRequestCall; + + final Runnable onCancelCall; + + FluxPeek(Flux source, + @Nullable Consumer onSubscribeCall, + @Nullable Consumer onNextCall, + @Nullable Consumer onErrorCall, + @Nullable Runnable onCompleteCall, + @Nullable Runnable onAfterTerminateCall, + @Nullable LongConsumer onRequestCall, + @Nullable Runnable onCancelCall) { + super(source); + this.onSubscribeCall = onSubscribeCall; + this.onNextCall = onNextCall; + this.onErrorCall = onErrorCall; + this.onCompleteCall = onCompleteCall; + this.onAfterTerminateCall = onAfterTerminateCall; + this.onRequestCall = onRequestCall; + this.onCancelCall = onCancelCall; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + @SuppressWarnings("unchecked") // javac, give reason to suppress because inference anomalies + ConditionalSubscriber s2 = (ConditionalSubscriber) actual; + return new PeekConditionalSubscriber<>(s2, this); + } + return new PeekSubscriber<>(actual, this); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class PeekSubscriber implements InnerOperator { + + final CoreSubscriber actual; + + final SignalPeek parent; + + Subscription s; + + boolean done; + + PeekSubscriber(CoreSubscriber actual, SignalPeek parent) { + this.actual = actual; + this.parent = parent; + } + + @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); + } + + @Override + public Context currentContext() { + Context c = actual.currentContext(); + if(!c.isEmpty() && parent.onCurrentContextCall() != null) { + parent.onCurrentContextCall().accept(c); + } + return c; + } + + @Override + public void request(long n) { + final LongConsumer requestHook = parent.onRequestCall(); + if (requestHook != null) { + try { + requestHook.accept(n); + } + catch (Throwable e) { + Operators.onOperatorError(e, actual.currentContext()); + } + } + s.request(n); + } + + @Override + public void cancel() { + final Runnable cancelHook = parent.onCancelCall(); + if (cancelHook != null) { + try { + cancelHook.run(); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + } + s.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if(Operators.validate(this.s, s)) { + final Consumer subscribeHook = parent.onSubscribeCall(); + if (subscribeHook != null) { + try { + subscribeHook.accept(s); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(s, e, + actual.currentContext())); + return; + } + } + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + final Consumer nextHook = parent.onNextCall(); + if(nextHook != null) { + try { + nextHook.accept(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ == null) { + request(1); + return; + } + else { + onError(e_); + return; + } + } + } + + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + final Consumer errorHook = parent.onErrorCall(); + if(errorHook != null) { + try { + errorHook.accept(t); + } + catch (Throwable e) { + //this performs a throwIfFatal or suppresses t in e + t = Operators.onOperatorError(null, e, t, actual.currentContext()); + } + } + + try { + actual.onError(t); + } + catch (UnsupportedOperationException use){ + if(errorHook == null + || !Exceptions.isErrorCallbackNotImplemented(use) && use.getCause() != t){ + throw use; + } + //ignore if missing callback + } + + final Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if(afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable e) { + afterErrorWithFailure(parent, e, t, actual.currentContext()); + } + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + final Runnable completeHook = parent.onCompleteCall(); + if(completeHook != null) { + try { + completeHook.run(); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + } + done = true; + + actual.onComplete(); + + final Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if(afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable e) { + afterCompleteWithFailure(parent, e, actual.currentContext()); + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + } + + @Override + @Nullable + public Consumer onSubscribeCall() { + return onSubscribeCall; + } + + @Override + @Nullable + public Consumer onNextCall() { + return onNextCall; + } + + @Override + @Nullable + public Consumer onErrorCall() { + return onErrorCall; + } + + @Override + @Nullable + public Runnable onCompleteCall() { + return onCompleteCall; + } + + @Override + @Nullable + public Runnable onAfterTerminateCall() { + return onAfterTerminateCall; + } + + @Override + @Nullable + public LongConsumer onRequestCall() { + return onRequestCall; + } + + @Override + @Nullable + public Runnable onCancelCall() { + return onCancelCall; + } + + /** + * Common method for FluxPeek and FluxPeekFuseable to deal with a doAfterTerminate + * callback that fails during onComplete. It drops the error to the global hook. + *

    + *
  • The callback failure is thrown immediately if fatal.
  • + *
  • {@link Operators#onOperatorError(Throwable, Context)} is called
  • + *
  • {@link Operators#onErrorDropped(Throwable, Context)} is called
  • + *
+ *

+ * + * @param parent the {@link SignalPeek} from which to get the callbacks + * @param callbackFailure the afterTerminate callback failure + * @param context subscriber context + */ + //see https://github.com/reactor/reactor-core/issues/270 + static void afterCompleteWithFailure(SignalPeek parent, + Throwable callbackFailure, Context context) { + + Exceptions.throwIfFatal(callbackFailure); + Throwable _e = Operators.onOperatorError(callbackFailure, context); + Operators.onErrorDropped(_e, context); + } + + /** + * Common method for FluxPeek and FluxPeekFuseable to deal with a doAfterTerminate + * callback that fails during onError. It drops the error to the global hook. + *

    + *
  • The callback failure is thrown immediately if fatal.
  • + *
  • {@link Operators#onOperatorError(Subscription, Throwable, Object, Context)} is + * called, adding the original error as suppressed
  • + *
  • {@link Operators#onErrorDropped(Throwable, Context)} is called
  • + *
+ *

+ * + * @param parent the {@link SignalPeek} from which to get the callbacks + * @param callbackFailure the afterTerminate callback failure + * @param originalError the onError throwable + * @param context subscriber context + */ + //see https://github.com/reactor/reactor-core/issues/270 + static void afterErrorWithFailure(SignalPeek parent, + Throwable callbackFailure, Throwable originalError, Context context) { + Exceptions.throwIfFatal(callbackFailure); + Throwable _e = Operators.onOperatorError(null, callbackFailure, originalError, context); + Operators.onErrorDropped(_e, context); + } + +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxPeekFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxPeekFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxPeekFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,959 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Peek into the lifecycle events and signals of a sequence. + *

+ *

+ * The callbacks are all optional. + *

+ *

+ * Crashes by the lambdas are ignored. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxPeekFuseable extends InternalFluxOperator + implements Fuseable, SignalPeek { + + final Consumer onSubscribeCall; + + final Consumer onNextCall; + + final Consumer onErrorCall; + + final Runnable onCompleteCall; + + final Runnable onAfterTerminateCall; + + final LongConsumer onRequestCall; + + final Runnable onCancelCall; + + FluxPeekFuseable(Flux source, + @Nullable Consumer onSubscribeCall, + @Nullable Consumer onNextCall, + @Nullable Consumer onErrorCall, + @Nullable Runnable onCompleteCall, + @Nullable Runnable onAfterTerminateCall, + @Nullable LongConsumer onRequestCall, + @Nullable Runnable onCancelCall) { + super(source); + + this.onSubscribeCall = onSubscribeCall; + this.onNextCall = onNextCall; + this.onErrorCall = onErrorCall; + this.onCompleteCall = onCompleteCall; + this.onAfterTerminateCall = onAfterTerminateCall; + this.onRequestCall = onRequestCall; + this.onCancelCall = onCancelCall; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new PeekFuseableConditionalSubscriber<>((ConditionalSubscriber) actual, this); + } + return new PeekFuseableSubscriber<>(actual, this); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class PeekFuseableSubscriber + implements InnerOperator, QueueSubscription { + + final CoreSubscriber actual; + + final SignalPeek parent; + + QueueSubscription s; + + int sourceMode; + + volatile boolean done; + + @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); + } + + PeekFuseableSubscriber(CoreSubscriber actual, + SignalPeek parent) { + this.actual = actual; + this.parent = parent; + } + + @Override + public Context currentContext() { + Context c = actual.currentContext(); + final Consumer contextHook = parent.onCurrentContextCall(); + if(!c.isEmpty() && contextHook != null) { + contextHook.accept(c); + } + return c; + } + + @Override + public void request(long n) { + final LongConsumer requestHook = parent.onRequestCall(); + if (requestHook != null) { + try { + requestHook.accept(n); + } + catch (Throwable e) { + Operators.onOperatorError(e, actual.currentContext()); + } + } + s.request(n); + } + + @Override + public void cancel() { + final Runnable cancelHook = parent.onCancelCall(); + if (cancelHook != null) { + try { + cancelHook.run(); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + } + s.cancel(); + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if(Operators.validate(this.s, s)) { + final Consumer subscribeHook = parent.onSubscribeCall(); + if (subscribeHook != null) { + try { + subscribeHook.accept(s); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(s, e, + actual.currentContext())); + return; + } + } + this.s = (QueueSubscription) s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + final Consumer nextHook = parent.onNextCall(); + if (nextHook != null) { + try { + nextHook.accept(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ == null) { + request(1); + return; + } + else { + onError(e_); + return; + } + } + } + actual.onNext(t); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + final Consumer errorHook = parent.onErrorCall(); + if (errorHook != null) { + Exceptions.throwIfFatal(t); + try { + errorHook.accept(t); + } + catch (Throwable e) { + //this performs a throwIfFatal or suppresses t in e + t = Operators.onOperatorError(null, e, t, actual.currentContext()); + } + } + + try { + actual.onError(t); + } + catch (UnsupportedOperationException use) { + if (errorHook == null + || !Exceptions.isErrorCallbackNotImplemented(use) && use.getCause() != t) { + throw use; + } + } + + final Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if (afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable e) { + FluxPeek.afterErrorWithFailure(parent, e, t, actual.currentContext()); + } + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + + if (sourceMode == ASYNC) { + done = true; + actual.onComplete(); + } + else { + final Runnable completeHook = parent.onCompleteCall(); + if (completeHook != null) { + try { + completeHook.run(); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + } + done = true; + + actual.onComplete(); + + final Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if (afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable e) { + FluxPeek.afterCompleteWithFailure(parent, e, actual.currentContext()); + } + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public T poll() { + boolean d = done; + T v; + try { + v = s.poll(); + } + catch (Throwable e) { + final Consumer errorHook = parent.onErrorCall(); + if (errorHook != null) { + try { + errorHook.accept(e); + } + catch (Throwable errorCallbackError) { + throw Exceptions.propagate(Operators.onOperatorError(s, errorCallbackError, e, + actual.currentContext())); + } + } + Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if (afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable afterTerminateCallbackError) { + throw Exceptions.propagate(Operators.onOperatorError(s, afterTerminateCallbackError, e, + actual.currentContext())); + } + } + throw Exceptions.propagate(Operators.onOperatorError(s, e, + actual.currentContext())); + } + + final Consumer nextHook = parent.onNextCall(); + if (v != null && nextHook != null) { + try { + nextHook.accept(v); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(v, e, actual.currentContext(), s); + if (e_ == null) { + return poll(); + } + else { + throw Exceptions.propagate(e_); + } + } + } + if (v == null && (d || sourceMode == SYNC)) { + Runnable call = parent.onCompleteCall(); + if (call != null) { + call.run(); + } + call = parent.onAfterTerminateCall(); + if (call != null) { + call.run(); + } + } + return v; + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + } + + static final class PeekFuseableConditionalSubscriber + implements ConditionalSubscriber, InnerOperator, + QueueSubscription { + + final ConditionalSubscriber actual; + + final SignalPeek parent; + + QueueSubscription s; + + int sourceMode; + + volatile boolean done; + + PeekFuseableConditionalSubscriber(ConditionalSubscriber actual, + SignalPeek parent) { + this.actual = actual; + this.parent = parent; + } + + @Override + public Context currentContext() { + Context c = actual.currentContext(); + final Consumer contextHook = parent.onCurrentContextCall(); + if(!c.isEmpty() && contextHook != null) { + contextHook.accept(c); + } + return c; + } + + @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); + } + + @Override + public void request(long n) { + final LongConsumer requestHook = parent.onRequestCall(); + if (requestHook != null) { + try { + requestHook.accept(n); + } + catch (Throwable e) { + Operators.onOperatorError(e, actual.currentContext()); + } + } + s.request(n); + } + + @Override + public void cancel() { + final Runnable cancelHook = parent.onCancelCall(); + if (cancelHook != null) { + try { + cancelHook.run(); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + } + s.cancel(); + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if(Operators.validate(this.s, s)) { + final Consumer subscribeHook = parent.onSubscribeCall(); + if (subscribeHook != null) { + try { + subscribeHook.accept(s); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(s, e, + actual.currentContext())); + return; + } + } + this.s = (QueueSubscription) s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + final Consumer nextHook = parent.onNextCall(); + if (nextHook != null) { + try { + nextHook.accept(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ == null) { + request(1); + return; + } + else { + onError(e_); + return; + } + } + } + actual.onNext(t); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return false; + } + + final Consumer nextHook = parent.onNextCall(); + if (nextHook != null) { + try { + nextHook.accept(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ == null) { + return false; + } + else { + onError(e_); + return true; + } + } + } + return actual.tryOnNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + final Consumer errorHook = parent.onErrorCall(); + if (errorHook != null) { + Exceptions.throwIfFatal(t); + try { + errorHook.accept(t); + } + catch (Throwable e) { + //this performs a throwIfFatal or suppresses t in e + t = Operators.onOperatorError(null, e, t, actual.currentContext()); + } + } + + try { + actual.onError(t); + } + catch (UnsupportedOperationException use) { + if (errorHook == null + || !Exceptions.isErrorCallbackNotImplemented(use) && use.getCause() != t) { + throw use; + } + } + + final Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if (afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable e) { + FluxPeek.afterErrorWithFailure(parent, e, t, actual.currentContext()); + } + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + + if (sourceMode == ASYNC) { + done = true; + actual.onComplete(); + } + else { + final Runnable completeHook = parent.onCompleteCall(); + if (completeHook != null) { + try { + completeHook.run(); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + } + done = true; + actual.onComplete(); + + final Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if (afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable e) { + FluxPeek.afterCompleteWithFailure(parent, e, actual.currentContext()); + } + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public T poll() { + boolean d = done; + T v; + try { + v = s.poll(); + } + catch (Throwable e) { + final Consumer errorHook = parent.onErrorCall(); + if (errorHook != null) { + try { + errorHook.accept(e); + } + catch (Throwable errorCallbackError) { + throw Exceptions.propagate(Operators.onOperatorError(s, errorCallbackError, e, + actual.currentContext())); + } + } + Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if (afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable afterTerminateCallbackError) { + throw Exceptions.propagate(Operators.onOperatorError(s, afterTerminateCallbackError, e, + actual.currentContext())); + } + } + throw Exceptions.propagate(Operators.onOperatorError(s, e, + actual.currentContext())); + } + + final Consumer nextHook = parent.onNextCall(); + if (v != null && nextHook != null) { + try { + nextHook.accept(v); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(v, e, actual.currentContext(), s); + if (e_ == null) { + return poll(); + } + else { + throw Exceptions.propagate(e_); + } + } + } + if (v == null && (d || sourceMode == SYNC)) { + Runnable call = parent.onCompleteCall(); + if (call != null) { + call.run(); + } + call = parent.onAfterTerminateCall(); + if (call != null) { + call.run(); + } + } + return v; + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if ((requestedMode & Fuseable.THREAD_BARRIER) != 0) { + return Fuseable.NONE; + } + else { + m = s.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + } + + @Override + @Nullable + public Consumer onSubscribeCall() { + return onSubscribeCall; + } + + @Override + @Nullable + public Consumer onNextCall() { + return onNextCall; + } + + @Override + @Nullable + public Consumer onErrorCall() { + return onErrorCall; + } + + @Override + @Nullable + public Runnable onCompleteCall() { + return onCompleteCall; + } + + @Override + @Nullable + public Runnable onAfterTerminateCall() { + return onAfterTerminateCall; + } + + @Override + @Nullable + public LongConsumer onRequestCall() { + return onRequestCall; + } + + @Override + @Nullable + public Runnable onCancelCall() { + return onCancelCall; + } + + static final class PeekConditionalSubscriber + implements ConditionalSubscriber, InnerOperator { + + final ConditionalSubscriber actual; + + final SignalPeek parent; + + Subscription s; + + boolean done; + + PeekConditionalSubscriber(ConditionalSubscriber actual, + SignalPeek parent) { + this.actual = actual; + this.parent = parent; + } + + @Override + public Context currentContext() { + Context c = actual.currentContext(); + if(!c.isEmpty() && parent.onCurrentContextCall() != null) { + parent.onCurrentContextCall().accept(c); + } + return c; + } + + @Override + public void request(long n) { + final LongConsumer requestHook = parent.onRequestCall(); + if (requestHook != null) { + try { + requestHook.accept(n); + } + catch (Throwable e) { + Operators.onOperatorError(e, actual.currentContext()); + } + } + s.request(n); + } + + @Override + public void cancel() { + final Runnable cancelHook = parent.onCancelCall(); + if (cancelHook != null) { + try { + cancelHook.run(); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + } + s.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + if(Operators.validate(this.s, s)) { + final Consumer subscribeHook = parent.onSubscribeCall(); + if (subscribeHook != null) { + try { + subscribeHook.accept(s); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(s, e, + actual.currentContext())); + return; + } + } + this.s = s; + actual.onSubscribe(this); + } + } + + @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); + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + final Consumer nextHook = parent.onNextCall(); + if (nextHook != null) { + try { + nextHook.accept(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ == null) { + request(1); + return; + } + else { + onError(e_); + return; + } + } + } + actual.onNext(t); + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return false; + } + + final Consumer nextHook = parent.onNextCall(); + if (nextHook != null) { + try { + nextHook.accept(t); + } + catch (Throwable e) { + Throwable e_ = Operators.onNextError(t, e, actual.currentContext(), s); + if (e_ == null) { + return false; + } + else { + onError(e_); + return true; + } + } + } + return actual.tryOnNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + final Consumer errorHook = parent.onErrorCall(); + if (errorHook != null) { + Exceptions.throwIfFatal(t); + try { + errorHook.accept(t); + } + catch (Throwable e) { + //this performs a throwIfFatal or suppresses t in e + t = Operators.onOperatorError(null, e, t, actual.currentContext()); + } + } + + try { + actual.onError(t); + } + catch (UnsupportedOperationException use) { + if (errorHook == null + || !Exceptions.isErrorCallbackNotImplemented(use) && use.getCause() != t) { + throw use; + } + } + + final Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if (afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable e) { + FluxPeek.afterErrorWithFailure(parent, e, t, actual.currentContext()); + } + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + final Runnable completeHook = parent.onCompleteCall(); + if (completeHook != null) { + try { + completeHook.run(); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + } + done = true; + + actual.onComplete(); + + final Runnable afterTerminateHook = parent.onAfterTerminateCall(); + if (afterTerminateHook != null) { + try { + afterTerminateHook.run(); + } + catch (Throwable e) { + FluxPeek.afterCompleteWithFailure(parent, e, actual.currentContext()); + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxProcessor.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2016-2021 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.CancellationException; +import java.util.stream.Stream; + +import org.reactivestreams.Processor; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +import static reactor.core.publisher.Sinks.Many; + +/** + * A base processor that exposes {@link Flux} API for {@link Processor}. + * + * Implementors include {@link UnicastProcessor}, {@link EmitterProcessor}, {@link ReplayProcessor}. + * + * @author Stephane Maldini + * + * @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 +public abstract class FluxProcessor extends Flux + implements Processor, CoreSubscriber, Scannable, Disposable, ContextHolder { + + /** + * Build a {@link FluxProcessor} whose data are emitted by the most recent emitted {@link Publisher}. + * The {@link Flux} will complete once both the publishers source and the last switched to {@link Publisher} have + * completed. + * + *

+ * + * + * @param the produced type + * @return a {@link FluxProcessor} accepting publishers and producing T + * @deprecated should use {@link Sinks}, {@link Many#asFlux()} and {@link Flux#switchOnNext(Publisher)}. To be removed in 3.5.0. + */ + @Deprecated + public static FluxProcessor, T> switchOnNext() { + UnicastProcessor> emitter = UnicastProcessor.create(); + FluxProcessor, T> p = FluxProcessor.wrap(emitter, switchOnNext(emitter)); + return p; + } + /** + * Transform a receiving {@link Subscriber} and a producing {@link Publisher} in a logical {@link FluxProcessor}. + * The link between the passed upstream and returned downstream will not be created automatically, e.g. not + * subscribed together. A {@link Processor} might choose to have orthogonal sequence input and output. + * + * @param the receiving type + * @param the producing type + * + * @param upstream the upstream subscriber + * @param downstream the downstream publisher + * @return a new blackboxed {@link FluxProcessor} + */ + public static FluxProcessor wrap(final Subscriber upstream, final Publisher downstream) { + return new DelegateProcessor<>(downstream, upstream); + } + + @Override + public void dispose() { + onError(new CancellationException("Disposed")); + } + + /** + * Return the number of active {@link Subscriber} or {@literal -1} if untracked. + * + * @return the number of active {@link Subscriber} or {@literal -1} if untracked + */ + public long downstreamCount(){ + return inners().count(); + } + + /** + * Return the processor buffer capacity if any or {@link Integer#MAX_VALUE} + * + * @return processor buffer capacity if any or {@link Integer#MAX_VALUE} + */ + public int getBufferSize() { + return Integer.MAX_VALUE; + } + + /** + * Current error if any, default to null + * + * @return Current error if any, default to null + */ + @Nullable + public Throwable getError() { + return null; + } + + /** + * Return true if any {@link Subscriber} is actively subscribed + * + * @return true if any {@link Subscriber} is actively subscribed + */ + public boolean hasDownstreams() { + return downstreamCount() != 0L; + } + + /** + * Return true if terminated with onComplete + * + * @return true if terminated with onComplete + */ + public final boolean hasCompleted() { + return isTerminated() && getError() == null; + } + + /** + * Return true if terminated with onError + * + * @return true if terminated with onError + */ + public final boolean hasError() { + return isTerminated() && getError() != null; + } + + @Override + public Stream inners() { + return Stream.empty(); + } + + /** + * Has this upstream finished or "completed" / "failed" ? + * + * @return has this upstream finished or "completed" / "failed" ? + */ + public boolean isTerminated() { + return false; + } + + /** + * Return true if this {@link FluxProcessor} supports multithread producing + * + * @return true if this {@link FluxProcessor} supports multithread producing + */ + public boolean isSerialized() { + return false; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return isTerminated(); + if (key == Attr.ERROR) return getError(); + if (key == Attr.CAPACITY) return getBufferSize(); + + return null; + } + + @Override + public Context currentContext() { + return Context.empty(); + } + + /** + * Create a {@link FluxProcessor} that safely gates multi-threaded producer + * {@link Subscriber#onNext(Object)}. + * + *

Discard Support: The resulting processor discards elements received from the source + * {@link Publisher} (if any) when it cancels subscription to said source. + * + * @return a serializing {@link FluxProcessor} + */ + public final FluxProcessor serialize() { + return new DelegateProcessor<>(this, Operators.serialize(this)); + } + + /** + * Create a {@link FluxSink} that safely gates multi-threaded producer + * {@link Subscriber#onNext(Object)}. This processor will be subscribed to + * that {@link FluxSink}, and any previous subscribers will be unsubscribed. + * + *

The returned {@link FluxSink} will not apply any + * {@link FluxSink.OverflowStrategy} and overflowing {@link FluxSink#next(Object)} + * will behave in two possible ways depending on the Processor: + *

    + *
  • an unbounded processor will handle the overflow itself by dropping or + * buffering
  • + *
  • a bounded processor will block/spin
  • + *
+ * + * @return a serializing {@link FluxSink} + * @deprecated To be removed in 3.5, prefer clear cut usage of {@link Sinks} + * through the {@link Sinks#many()} spec. + */ + @Deprecated + public final FluxSink sink() { + return sink(FluxSink.OverflowStrategy.IGNORE); + } + + /** + * Create a {@link FluxSink} that safely gates multi-threaded producer + * {@link Subscriber#onNext(Object)}. This processor will be subscribed to + * that {@link FluxSink}, and any previous subscribers will be unsubscribed. + * + *

The returned {@link FluxSink} will not apply any + * {@link FluxSink.OverflowStrategy} and overflowing {@link FluxSink#next(Object)} + * will behave in two possible ways depending on the Processor: + *

    + *
  • an unbounded processor will handle the overflow itself by dropping or + * buffering
  • + *
  • a bounded processor will block/spin on IGNORE strategy, or apply the + * strategy behavior
  • + *
+ * + * @param strategy the overflow strategy, see {@link FluxSink.OverflowStrategy} + * for the + * available strategies + * @return a serializing {@link FluxSink} + * @deprecated To be removed in 3.5, prefer clear cut usage of {@link Sinks} + * through the {@link Sinks#many()} spec. + */ + @Deprecated + public final FluxSink sink(FluxSink.OverflowStrategy strategy) { + Objects.requireNonNull(strategy, "strategy"); + if (getBufferSize() == Integer.MAX_VALUE){ + strategy = FluxSink.OverflowStrategy.IGNORE; + } + + FluxCreate.BaseSink s = FluxCreate.createSink(this, strategy); + onSubscribe(s); + + if(s.isCancelled() || + (isSerialized() && getBufferSize() == Integer.MAX_VALUE)){ + return s; + } + if (serializeAlways()) + return new FluxCreate.SerializedFluxSink<>(s); + else + return new FluxCreate.SerializeOnRequestSink<>(s); + } + + /** + * Returns serialization strategy. If true, {@link FluxProcessor#sink()} will always + * be serialized. Otherwise sink is serialized only if {@link FluxSink#onRequest(java.util.function.LongConsumer)} + * is invoked. + * @return true to serialize any sink, false to delay serialization till onRequest + */ + protected boolean serializeAlways() { + return true; + } + + /** + * Return true if {@code FluxProcessor} + * + * @return true if {@code FluxProcessor} + */ + protected boolean isIdentityProcessor() { + return false; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxPublish.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxPublish.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxPublish.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,657 @@ +/* + * Copyright (c) 2016-2021 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.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; + +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.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A connectable publisher which shares an underlying source and dispatches source values + * to subscribers in a backpressure-aware manner. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxPublish extends ConnectableFlux implements Scannable { + + /** + * The source observable. + */ + final Flux source; + + /** + * The size of the prefetch buffer. + */ + final int prefetch; + + final Supplier> queueSupplier; + + volatile PublishSubscriber connection; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater CONNECTION = + AtomicReferenceFieldUpdater.newUpdater(FluxPublish.class, + PublishSubscriber.class, + "connection"); + + FluxPublish(Flux source, + int prefetch, + Supplier> queueSupplier) { + 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"); + } + + @Override + public void connect(Consumer cancelSupport) { + boolean doConnect; + PublishSubscriber s; + for (; ; ) { + s = connection; + if (s == null || s.isTerminated()) { + PublishSubscriber u = new PublishSubscriber<>(prefetch, this); + + if (!CONNECTION.compareAndSet(this, s, u)) { + continue; + } + + s = u; + } + + doConnect = s.tryConnect(); + break; + } + + cancelSupport.accept(s); + if (doConnect) { + source.subscribe(s); + } + } + + @Override + public void subscribe(CoreSubscriber actual) { + PublishInner inner = new PublishInner<>(actual); + actual.onSubscribe(inner); + for (; ; ) { + if (inner.isCancelled()) { + break; + } + + PublishSubscriber c = connection; + if (c == null || c.isTerminated()) { + PublishSubscriber u = new PublishSubscriber<>(prefetch, this); + if (!CONNECTION.compareAndSet(this, c, u)) { + continue; + } + + c = u; + } + + if (c.add(inner)) { + if (inner.isCancelled()) { + c.remove(inner); + } + else { + inner.parent = c; + } + c.drain(); + break; + } + } + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + static final class PublishSubscriber + implements InnerConsumer, Disposable { + + final int prefetch; + + final FluxPublish parent; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(PublishSubscriber.class, + Subscription.class, + "s"); + + volatile PubSubInner[] subscribers; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater SUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater(PublishSubscriber.class, + PubSubInner[].class, + "subscribers"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(PublishSubscriber.class, "wip"); + + 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") + static final PubSubInner[] INIT = new PublishInner[0]; + @SuppressWarnings("rawtypes") + static final PubSubInner[] CANCELLED = new PublishInner[0]; + @SuppressWarnings("rawtypes") + static final PubSubInner[] TERMINATED = new PublishInner[0]; + + volatile Queue queue; + + int sourceMode; + + volatile boolean done; + + volatile Throwable error; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(PublishSubscriber.class, + Throwable.class, + "error"); + + @SuppressWarnings("unchecked") + PublishSubscriber(int prefetch, FluxPublish parent) { + this.prefetch = prefetch; + this.parent = parent; + SUBSCRIBERS.lazySet(this, INIT); + } + + boolean isTerminated(){ + return subscribers == TERMINATED; + } + + @Override + public void onSubscribe(Subscription s) { + 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 | Fuseable.THREAD_BARRIER); + if (m == Fuseable.SYNC) { + sourceMode = m; + queue = f; + drain(); + return; + } + if (m == Fuseable.ASYNC) { + sourceMode = m; + queue = f; + s.request(Operators.unboundedOrPrefetch(prefetch)); + return; + } + } + + queue = parent.queueSupplier.get(); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if (done) { + if (t != null) { + Operators.onNextDropped(t, currentContext()); + } + return; + } + if (sourceMode == Fuseable.ASYNC) { + drain(); + return; + } + + if (!queue.offer(t)) { + Throwable ex = Operators.onOperatorError(s, + Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t, currentContext()); + if (!Exceptions.addThrowable(ERROR, this, ex)) { + Operators.onErrorDroppedMulticast(ex, subscribers); + return; + } + done = true; + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDroppedMulticast(t, subscribers); + return; + } + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(); + } + else { + Operators.onErrorDroppedMulticast(t, subscribers); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + drain(); + } + + @Override + public void dispose() { + if (SUBSCRIBERS.get(this) == TERMINATED) { + return; + } + if (CONNECTION.compareAndSet(parent, this, null)) { + Operators.terminate(S, this); + if (WIP.getAndIncrement(this) != 0) { + return; + } + disconnectAction(); + } + } + + void disconnectAction() { + @SuppressWarnings("unchecked") + PubSubInner[] inners = SUBSCRIBERS.getAndSet(this, CANCELLED); + if (inners.length > 0) { + queue.clear(); + CancellationException ex = new CancellationException("Disconnected"); + + for (PubSubInner inner : inners) { + inner.actual.onError(ex); + } + } + } + + boolean add(PublishInner inner) { + for (; ; ) { + FluxPublish.PubSubInner[] a = subscribers; + if (a == TERMINATED) { + return false; + } + int n = a.length; + PubSubInner[] b = new PubSubInner[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + public void remove(PubSubInner inner) { + for (; ; ) { + PubSubInner[] a = subscribers; + if (a == TERMINATED || a == CANCELLED) { + 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) { + //inner was not found + return; + } + + PubSubInner[] b; + if (n == 1) { + b = CANCELLED; + } + else { + b = new 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)) { + //we don't assume autoCancel semantics, which will rather depend from + //downstream operators like autoConnect vs refCount, so we don't disconnect here + return; + } + } + } + + @SuppressWarnings("unchecked") + PubSubInner[] terminate() { + return SUBSCRIBERS.getAndSet(this, TERMINATED); + } + + boolean tryConnect() { + return connected == 0 && CONNECTED.compareAndSet(this, 0, 1); + } + + 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; + } + + PubSubInner[] a = subscribers; + + if (a != CANCELLED && !empty) { + long maxRequested = Long.MAX_VALUE; + + int len = a.length; + int cancel = 0; + + for (PubSubInner inner : a) { + long r = inner.requested; + if (r >= 0L) { + maxRequested = Math.min(maxRequested, r); + } + else { //Long.MIN + 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) { + done = true; + checkTerminated(true, true); + } + break; + } + + for (PubSubInner inner : a) { + inner.actual.onNext(v); + if(Operators.producedCancellable(PubSubInner.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)) { + break; + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty) { + if (s == Operators.cancelledSubscription()) { + disconnectAction(); + return true; + } + if (d) { + Throwable e = error; + if (e != null && e != Exceptions.TERMINATED) { + 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); + for (PubSubInner inner : terminate()) { + inner.actual.onComplete(); + } + return true; + } + } + return false; + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.ERROR) return error; + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.TERMINATED) return isTerminated(); + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public boolean isDisposed() { + return s == Operators.cancelledSubscription() || done; + } + + } + + static abstract class PubSubInner implements InnerProducer { + + final CoreSubscriber actual; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(PubSubInner.class, "requested"); + + PubSubInner(CoreSubscriber actual) { + this.actual = actual; + } + + @Override + public final void request(long n) { + if (Operators.validate(n)) { + Operators.addCapCancellable(REQUESTED, this, n); + drainParent(); + } + } + + @Override + public final void cancel() { + long r = requested; + if (r != Long.MIN_VALUE) { + r = REQUESTED.getAndSet(this, Long.MIN_VALUE); + if (r != Long.MIN_VALUE) { + removeAndDrainParent(); + } + } + } + + final boolean isCancelled() { + return requested == Long.MIN_VALUE; + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return isCancelled(); + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return isCancelled() ? 0L : requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + abstract void drainParent(); + abstract void removeAndDrainParent(); + } + + static final class PublishInner extends PubSubInner { + PublishSubscriber parent; + + PublishInner(CoreSubscriber actual) { + super(actual); + } + + @Override + void drainParent() { + PublishSubscriber p = parent; + if (p != null) { + p.drain(); + } + } + + @Override + void removeAndDrainParent() { + PublishSubscriber p = parent; + if (p != null) { + p.remove(this); + p.drain(); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.TERMINATED) return parent != null && parent.isTerminated(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxPublishMulticast.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxPublishMulticast.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxPublishMulticast.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,886 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; +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; + +import static reactor.core.Scannable.Attr.RUN_STYLE; +import static reactor.core.Scannable.Attr.RunStyle.SYNC; + +/** + * Shares a sequence for the duration of a function that may transform it and consume it + * as many times as necessary without causing multiple subscriptions to the upstream. + * + * @param the source value type + * @param the output value type + * + * @see Reactive-Streams-Commons + */ +final class FluxPublishMulticast extends InternalFluxOperator implements Fuseable { + + final Function, ? extends Publisher> transform; + + final Supplier> queueSupplier; + + final int prefetch; + + FluxPublishMulticast(Flux source, + Function, ? extends Publisher> transform, + int prefetch, + Supplier> queueSupplier) { + super(source); + if (prefetch < 1) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.prefetch = prefetch; + this.transform = Objects.requireNonNull(transform, "transform"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + FluxPublishMulticaster multicast = new FluxPublishMulticaster<>(prefetch, + queueSupplier, + actual.currentContext()); + + Publisher out = Objects.requireNonNull(transform.apply(multicast), + "The transform returned a null Publisher"); + + if (out instanceof Fuseable) { + out.subscribe(new CancelFuseableMulticaster<>(actual, multicast)); + } + else { + out.subscribe(new CancelMulticaster<>(actual, multicast)); + } + + return multicast; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class FluxPublishMulticaster extends Flux + implements InnerConsumer, PublishMulticasterParent { + + final int limit; + + final int prefetch; + + final Supplier> queueSupplier; + + Queue queue; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(FluxPublishMulticaster.class, + Subscription.class, + "s"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(FluxPublishMulticaster.class, "wip"); + + volatile PublishMulticastInner[] subscribers; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + SUBSCRIBERS = AtomicReferenceFieldUpdater.newUpdater( + FluxPublishMulticaster.class, + PublishMulticastInner[].class, + "subscribers"); + + @SuppressWarnings("rawtypes") + static final PublishMulticastInner[] EMPTY = new PublishMulticastInner[0]; + + @SuppressWarnings("rawtypes") + static final PublishMulticastInner[] TERMINATED = new PublishMulticastInner[0]; + + volatile boolean done; + + volatile boolean connected; + + Throwable error; + + final Context context; + + int produced; + + int sourceMode; + + @SuppressWarnings("unchecked") + FluxPublishMulticaster(int prefetch, + Supplier> queueSupplier, + Context ctx) { + this.prefetch = prefetch; + this.limit = Operators.unboundedOrLimit(prefetch); + this.queueSupplier = queueSupplier; + SUBSCRIBERS.lazySet(this, EMPTY); + this.context = ctx; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.ERROR) { + return error; + } + if (key == Attr.CANCELLED) { + return s == Operators.cancelledSubscription(); + } + if (key == Attr.TERMINATED) { + return done; + } + if (key == Attr.PREFETCH) { + return prefetch; + } + if (key == Attr.BUFFERED) { + return queue != null ? queue.size() : 0; + } + if (key == RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return null; + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public Context currentContext() { + return context; + } + + @Override + public void subscribe(CoreSubscriber actual) { + PublishMulticastInner pcs = new PublishMulticastInner<>(this, actual); + actual.onSubscribe(pcs); + + if (add(pcs)) { + if (pcs.requested == Long.MIN_VALUE) { + remove(pcs); + return; + } + drain(); + } + else { + Throwable ex = error; + if (ex != null) { + actual.onError(ex); + } + else { + actual.onComplete(); + } + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") QueueSubscription qs = + (QueueSubscription) s; + + int m = qs.requestFusion(Fuseable.ANY); + if (m == Fuseable.SYNC) { + sourceMode = m; + + queue = qs; + done = true; + connected = true; + + drain(); + + return; + } + if (m == Fuseable.ASYNC) { + sourceMode = m; + + queue = qs; + connected = true; + + s.request(Operators.unboundedOrPrefetch(prefetch)); + + return; + } + } + + queue = queueSupplier.get(); + connected = true; + + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, context); + return; + } + + if (sourceMode != Fuseable.ASYNC) { + if (!queue.offer(t)) { + onError(Operators.onOperatorError(s, + Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), + t, + context)); + return; + } + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, context); + return; + } + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + if (sourceMode == Fuseable.SYNC) { + drainSync(); + } + else { + drainAsync(); + } + } + + @SuppressWarnings("unchecked") + void drainSync() { + int missed = 1; + + for (; ; ) { + + if (connected) { + + if (s == Operators.cancelledSubscription()) { + queue.clear(); + return; + } + + final Queue queue = this.queue; + + PublishMulticastInner[] a = subscribers; + int n = a.length; + + if (n != 0) { + + long r = Long.MAX_VALUE; + long u; + for (int i = 0; i < n; i++) { + u = a[i].requested; + if (u != Long.MIN_VALUE) { + r = Math.min(r, u); + } + } + + long e = 0L; + + while (e != r) { + + if (s == Operators.cancelledSubscription()) { + queue.clear(); + return; + } + + T v; + + try { + v = queue.poll(); + } + catch (Throwable ex) { + error = Operators.onOperatorError(s, ex, context); + queue.clear(); + a = SUBSCRIBERS.getAndSet(this, TERMINATED); + n = a.length; + for (int i = 0; i < n; i++) { + a[i].actual.onError(ex); + } + return; + } + + if (v == null) { + a = SUBSCRIBERS.getAndSet(this, TERMINATED); + n = a.length; + for (int i = 0; i < n; i++) { + a[i].actual.onComplete(); + } + return; + } + + for (int i = 0; i < n; i++) { + a[i].actual.onNext(v); + } + + e++; + } + + if (s == Operators.cancelledSubscription()) { + queue.clear(); + return; + } + if (queue.isEmpty()) { + a = SUBSCRIBERS.getAndSet(this, TERMINATED); + n = a.length; + for (int i = 0; i < n; i++) { + a[i].actual.onComplete(); + } + return; + } + + if (e != 0L) { + for (int i = 0; i < n; i++) { + a[i].produced(e); + } + } + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + @SuppressWarnings("unchecked") + void drainAsync() { + int missed = 1; + + int p = produced; + + for (; ; ) { + + if (connected) { + if (s == Operators.cancelledSubscription()) { + queue.clear(); + return; + } + + final Queue queue = this.queue; + + PublishMulticastInner[] a = subscribers; + int n = a.length; + + if (n != 0) { + + long r = Long.MAX_VALUE; + long u; + for (int i = 0; i < n; i++) { + u = a[i].requested; + if (u != Long.MIN_VALUE) { + r = Math.min(r, u); + } + } + + long e = 0L; + + while (e != r) { + if (s == Operators.cancelledSubscription()) { + queue.clear(); + return; + } + + boolean d = done; + + T v; + + try { + v = queue.poll(); + } + catch (Throwable ex) { + queue.clear(); + error = Operators.onOperatorError(s, ex, context); + a = SUBSCRIBERS.getAndSet(this, TERMINATED); + n = a.length; + for (int i = 0; i < n; i++) { + a[i].actual.onError(ex); + } + return; + } + + boolean empty = v == null; + + if (d) { + Throwable ex = error; + if (ex != null) { + queue.clear(); + a = SUBSCRIBERS.getAndSet(this, TERMINATED); + n = a.length; + for (int i = 0; i < n; i++) { + a[i].actual.onError(ex); + } + return; + } + + if (empty) { + a = SUBSCRIBERS.getAndSet(this, TERMINATED); + n = a.length; + for (int i = 0; i < n; i++) { + a[i].actual.onComplete(); + } + return; + } + } + + if (empty) { + break; + } + + for (int i = 0; i < n; i++) { + a[i].actual.onNext(v); + } + + e++; + + if (++p == limit) { + s.request(p); + p = 0; + } + } + + if (e == r) { + if (s == Operators.cancelledSubscription()) { + queue.clear(); + return; + } + + boolean d = done; + + if (d) { + Throwable ex = error; + if (ex != null) { + queue.clear(); + a = SUBSCRIBERS.getAndSet(this, TERMINATED); + n = a.length; + for (int i = 0; i < n; i++) { + a[i].actual.onError(ex); + } + return; + } + + if (queue.isEmpty()) { + a = SUBSCRIBERS.getAndSet(this, TERMINATED); + n = a.length; + for (int i = 0; i < n; i++) { + a[i].actual.onComplete(); + } + return; + } + } + + } + + if (e != 0L) { + for (int i = 0; i < n; i++) { + a[i].produced(e); + } + } + } + + } + + produced = p; + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean add(PublishMulticastInner s) { + for (; ; ) { + PublishMulticastInner[] a = subscribers; + + if (a == TERMINATED) { + return false; + } + + int n = a.length; + + @SuppressWarnings("unchecked") PublishMulticastInner[] b = + new PublishMulticastInner[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = s; + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(PublishMulticastInner s) { + for (; ; ) { + PublishMulticastInner[] 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] == s) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + PublishMulticastInner[] b; + if (n == 1) { + b = EMPTY; + } + else { + b = new PublishMulticastInner[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)) { + return; + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void terminate() { + Operators.terminate(S, this); + if (WIP.getAndIncrement(this) == 0) { + if (connected) { + queue.clear(); + } + } + } + } + + static final class PublishMulticastInner implements InnerProducer { + + final FluxPublishMulticaster parent; + + final CoreSubscriber actual; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(PublishMulticastInner.class, + "requested"); + + PublishMulticastInner(FluxPublishMulticaster parent, + CoreSubscriber actual) { + this.parent = parent; + this.actual = actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) { + return Math.max(0L, requested); + } + if (key == Attr.PARENT) { + return parent; + } + if (key == Attr.CANCELLED) { + return Long.MIN_VALUE == requested; + } + if (key == RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCapCancellable(REQUESTED, this, n); + parent.drain(); + } + } + + @Override + public void cancel() { + if (REQUESTED.getAndSet(this, Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + parent.drain(); + } + } + + void produced(long n) { + Operators.producedCancellable(REQUESTED, this, n); + } + } + + interface PublishMulticasterParent { + + void terminate(); + + } + + static final class CancelMulticaster + implements InnerOperator, QueueSubscription { + + final CoreSubscriber actual; + + final PublishMulticasterParent parent; + + Subscription s; + + CancelMulticaster(CoreSubscriber actual, + PublishMulticasterParent parent) { + this.actual = actual; + this.parent = parent; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + parent.terminate(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + parent.terminate(); + } + + @Override + public void onComplete() { + actual.onComplete(); + parent.terminate(); + } + + @Override + public int requestFusion(int requestedMode) { + return NONE; + } + + @Override + public void clear() { + // should not be called because fusion is always rejected + } + + @Override + public boolean isEmpty() { + // should not be called because fusion is always rejected + return false; + } + + @Override + public int size() { + // should not be called because fusion is always rejected + return 0; + } + + @Override + @Nullable + public T poll() { + // should not be called because fusion is always rejected + return null; + } + } + + static final class CancelFuseableMulticaster + implements InnerOperator, QueueSubscription { + + final CoreSubscriber actual; + + final PublishMulticasterParent parent; + + QueueSubscription s; + + CancelFuseableMulticaster(CoreSubscriber actual, + PublishMulticasterParent parent) { + this.actual = actual; + this.parent = parent; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + parent.terminate(); + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = Operators.as(s); + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + parent.terminate(); + } + + @Override + public void onComplete() { + actual.onComplete(); + parent.terminate(); + } + + @Override + public int requestFusion(int requestedMode) { + return s.requestFusion(requestedMode); + } + + @Override + @Nullable + public T poll() { + return s.poll(); + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + public int size() { + return s.size(); + } + + @Override + public void clear() { + s.clear(); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxPublishOn.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxPublishOn.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxPublishOn.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,1221 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Supplier; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Scheduler.Worker; +import reactor.util.annotation.Nullable; + +/** + * Emits events on a different thread specified by a scheduler callback. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxPublishOn extends InternalFluxOperator implements Fuseable { + + final Scheduler scheduler; + + final boolean delayError; + + final Supplier> queueSupplier; + + final int prefetch; + + final int lowTide; + + FluxPublishOn(Flux source, + Scheduler scheduler, + boolean delayError, + int prefetch, + int lowTide, + Supplier> queueSupplier) { + super(source); + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); + this.delayError = delayError; + this.prefetch = prefetch; + this.lowTide = lowTide; + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return super.scanUnsafe(key); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + Worker worker = Objects.requireNonNull(scheduler.createWorker(), + "The scheduler returned a null worker"); + + if (actual instanceof ConditionalSubscriber) { + ConditionalSubscriber cs = (ConditionalSubscriber) actual; + source.subscribe(new PublishOnConditionalSubscriber<>(cs, + scheduler, + worker, + delayError, + prefetch, + lowTide, + queueSupplier)); + return null; + } + return new PublishOnSubscriber<>(actual, + scheduler, + worker, + delayError, + prefetch, + lowTide, + queueSupplier); + } + + static final class PublishOnSubscriber + implements QueueSubscription, Runnable, InnerOperator { + + final CoreSubscriber actual; + + final Scheduler scheduler; + + final Worker worker; + + final boolean delayError; + + final int prefetch; + + final int limit; + + final Supplier> queueSupplier; + + Subscription s; + + Queue queue; + + volatile boolean cancelled; + + volatile boolean done; + + Throwable error; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(PublishOnSubscriber.class, "wip"); + + volatile int discardGuard; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater DISCARD_GUARD = + AtomicIntegerFieldUpdater.newUpdater(PublishOnSubscriber.class, "discardGuard"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(PublishOnSubscriber.class, "requested"); + + int sourceMode; + + long produced; + + boolean outputFused; + + PublishOnSubscriber(CoreSubscriber actual, + Scheduler scheduler, + Worker worker, + boolean delayError, + int prefetch, + int lowTide, + Supplier> queueSupplier) { + this.actual = actual; + this.worker = worker; + this.scheduler = scheduler; + this.delayError = delayError; + this.prefetch = prefetch; + this.queueSupplier = queueSupplier; + this.limit = Operators.unboundedOrLimit(prefetch, lowTide); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") QueueSubscription f = + (QueueSubscription) s; + + int m = f.requestFusion(Fuseable.ANY | Fuseable.THREAD_BARRIER); + + if (m == Fuseable.SYNC) { + sourceMode = Fuseable.SYNC; + queue = f; + done = true; + + actual.onSubscribe(this); + return; + } + if (m == Fuseable.ASYNC) { + sourceMode = Fuseable.ASYNC; + queue = f; + + actual.onSubscribe(this); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + + return; + } + } + + queue = queueSupplier.get(); + + actual.onSubscribe(this); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + trySchedule(this, null, null /* t always null */); + return; + } + + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + if (cancelled) { + Operators.onDiscard(t, actual.currentContext()); + return; + } + + if (!queue.offer(t)) { + Operators.onDiscard(t, actual.currentContext()); + error = Operators.onOperatorError(s, + Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), + t, actual.currentContext()); + done = true; + } + trySchedule(this, null, t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + error = t; + done = true; + trySchedule(null, t, null); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + //WIP also guards, no competing onNext + trySchedule(null, null, null); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + //WIP also guards during request and onError is possible + trySchedule(this, null, null); + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + + cancelled = true; + s.cancel(); + worker.dispose(); + + if (WIP.getAndIncrement(this) == 0) { + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } + else 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, actual.currentContext(), null); + } + } + } + + void trySchedule( + @Nullable Subscription subscription, + @Nullable Throwable suppressed, + @Nullable Object dataSignal) { + if (WIP.getAndIncrement(this) != 0) { + if (cancelled) { + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } + else { + // discard given dataSignal since no more is enqueued (spec guarantees serialised onXXX calls) + Operators.onDiscard(dataSignal, actual.currentContext()); + } + } + return; + } + + try { + worker.schedule(this); + } + catch (RejectedExecutionException ree) { + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } else if (outputFused) { + // 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(); + } + else { + // In all other modes we are free to discard queue immediately since there is no racing on pooling + Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); + } + actual.onError(Operators.onRejectedExecution(ree, subscription, suppressed, dataSignal, + actual.currentContext())); + } + } + + void runSync() { + int missed = 1; + + final Subscriber a = actual; + final Queue q = queue; + + long e = produced; + + for (; ; ) { + + long r = requested; + + while (e != r) { + T v; + + try { + v = q.poll(); + } + catch (Throwable ex) { + doError(a, Operators.onOperatorError(s, ex, + actual.currentContext())); + return; + } + + if (cancelled) { + Operators.onDiscard(v, actual.currentContext()); + Operators.onDiscardQueueWithClear(q, actual.currentContext(), null); + return; + } + if (v == null) { + doComplete(a); + return; + } + + a.onNext(v); + + e++; + } + + if (cancelled) { + Operators.onDiscardQueueWithClear(q, actual.currentContext(), null); + return; + } + + if (q.isEmpty()) { + doComplete(a); + return; + } + + int w = wip; + if (missed == w) { + produced = e; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + else { + missed = w; + } + } + } + + void runAsync() { + int missed = 1; + + final Subscriber a = actual; + final Queue q = queue; + + long e = produced; + + for (; ; ) { + + long r = requested; + + while (e != r) { + boolean d = done; + T v; + + try { + v = q.poll(); + } + catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + s.cancel(); + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } else { + // discard MUST be happening only and only if there is no racing on elements consumption + // which is guaranteed by the WIP guard here + Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); + } + + doError(a, Operators.onOperatorError(ex, actual.currentContext())); + return; + } + + boolean empty = v == null; + + if (checkTerminated(d, empty, a, v)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + if (e == limit) { + if (r != Long.MAX_VALUE) { + r = REQUESTED.addAndGet(this, -e); + } + s.request(e); + e = 0L; + } + } + + if (e == r && checkTerminated(done, q.isEmpty(), a, null)) { + return; + } + + int w = wip; + if (missed == w) { + produced = e; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + else { + missed = w; + } + } + } + + void runBackfused() { + 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(); + return; + } + + boolean d = done; + + actual.onNext(null); + + if (d) { + Throwable e = error; + if (e != null) { + doError(actual, e); + } + else { + doComplete(actual); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void doComplete(Subscriber a) { + a.onComplete(); + worker.dispose(); + } + + void doError(Subscriber a, Throwable e) { + try { + a.onError(e); + } + finally { + worker.dispose(); + } + } + + @Override + public void run() { + if (outputFused) { + runBackfused(); + } + else if (sourceMode == Fuseable.SYNC) { + runSync(); + } + else { + runAsync(); + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, @Nullable T v) { + if (cancelled) { + Operators.onDiscard(v, actual.currentContext()); + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } else { + // discard MUST be happening only and only if there is no racing on elements consumption + // which is guaranteed by the WIP guard here + Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); + } + return true; + } + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + doError(a, e); + } + else { + doComplete(a); + } + return true; + } + } + else { + Throwable e = error; + if (e != null) { + Operators.onDiscard(v, actual.currentContext()); + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } else { + // discard MUST be happening only and only if there is no racing on elements consumption + // which is guaranteed by the WIP guard here + Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); + } + doError(a, e); + return true; + } + else if (empty) { + doComplete(a); + return true; + } + } + } + + return false; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.REQUESTED_FROM_DOWNSTREAM ) return requested; + if (key == Attr.PARENT ) return s; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.TERMINATED) return done; + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.ERROR) return error; + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.RUN_ON) return worker; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @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, actual.currentContext(), null); + + int dg = discardGuard; + if (missed == dg) { + missed = DISCARD_GUARD.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + else { + missed = dg; + } + } + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + @Nullable + public T poll() { + T v = queue.poll(); + if (v != null && sourceMode != SYNC) { + long p = produced + 1; + if (p == limit) { + produced = 0; + s.request(p); + } + else { + produced = p; + } + } + return v; + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Override + public int size() { + return queue.size(); + } + } + + static final class PublishOnConditionalSubscriber + implements QueueSubscription, Runnable, InnerOperator { + + final ConditionalSubscriber actual; + + final Worker worker; + + final Scheduler scheduler; + + final boolean delayError; + + final int prefetch; + + final int limit; + + final Supplier> queueSupplier; + + Subscription s; + + Queue queue; + + volatile boolean cancelled; + + volatile boolean done; + + Throwable error; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(PublishOnConditionalSubscriber.class, + "wip"); + + volatile int discardGuard; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater DISCARD_GUARD = + AtomicIntegerFieldUpdater.newUpdater(PublishOnConditionalSubscriber.class, + "discardGuard"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(PublishOnConditionalSubscriber.class, + "requested"); + + int sourceMode; + + long produced; + + long consumed; + + boolean outputFused; + + PublishOnConditionalSubscriber(ConditionalSubscriber actual, + Scheduler scheduler, + Worker worker, + boolean delayError, + int prefetch, + int lowTide, + Supplier> queueSupplier) { + this.actual = actual; + this.worker = worker; + this.scheduler = scheduler; + this.delayError = delayError; + this.prefetch = prefetch; + this.queueSupplier = queueSupplier; + this.limit = Operators.unboundedOrLimit(prefetch, lowTide); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") QueueSubscription f = + (QueueSubscription) s; + + int m = f.requestFusion(Fuseable.ANY | Fuseable.THREAD_BARRIER); + + if (m == Fuseable.SYNC) { + sourceMode = Fuseable.SYNC; + queue = f; + done = true; + + actual.onSubscribe(this); + return; + } + if (m == Fuseable.ASYNC) { + sourceMode = Fuseable.ASYNC; + queue = f; + + actual.onSubscribe(this); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + + return; + } + } + + queue = queueSupplier.get(); + + actual.onSubscribe(this); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + trySchedule(this, null, null /* t always null */); + return; + } + + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + if (cancelled) { + Operators.onDiscard(t, actual.currentContext()); + return; + } + + if (!queue.offer(t)) { + Operators.onDiscard(t, actual.currentContext()); + error = Operators.onOperatorError(s, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t, + actual.currentContext()); + done = true; + } + trySchedule(this, null, t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + error = t; + done = true; + trySchedule(null, t, null); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + trySchedule(null, null, null); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + trySchedule(this, null, null); + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + + cancelled = true; + s.cancel(); + worker.dispose(); + + if (WIP.getAndIncrement(this) == 0) { + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } + else 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, actual.currentContext(), null); + } + } + } + + void trySchedule( + @Nullable Subscription subscription, + @Nullable Throwable suppressed, + @Nullable Object dataSignal) { + if (WIP.getAndIncrement(this) != 0) { + if (cancelled) { + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } + else { + // discard given dataSignal since no more is enqueued (spec guarantees serialised onXXX calls) + Operators.onDiscard(dataSignal, actual.currentContext()); + } + } + return; + } + + try { + worker.schedule(this); + } + catch (RejectedExecutionException ree) { + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } else if (outputFused) { + // 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(); + } + else { + // In all other modes we are free to discard queue immediately since there is no racing on pooling + Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); + } + actual.onError(Operators.onRejectedExecution(ree, subscription, suppressed, dataSignal, + actual.currentContext())); + } + } + + void runSync() { + int missed = 1; + + final ConditionalSubscriber a = actual; + final Queue q = queue; + + long e = produced; + + for (; ; ) { + + long r = requested; + + while (e != r) { + T v; + try { + v = q.poll(); + } + catch (Throwable ex) { + doError(a, Operators.onOperatorError(s, ex, + actual.currentContext())); + return; + } + + if (cancelled) { + Operators.onDiscard(v, actual.currentContext()); + Operators.onDiscardQueueWithClear(q, actual.currentContext(), null); + return; + } + if (v == null) { + doComplete(a); + return; + } + + if (a.tryOnNext(v)) { + e++; + } + } + + if (cancelled) { + Operators.onDiscardQueueWithClear(q, actual.currentContext(), null); + return; + } + + if (q.isEmpty()) { + doComplete(a); + return; + } + + int w = wip; + if (missed == w) { + produced = e; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + else { + missed = w; + } + } + } + + void runAsync() { + int missed = 1; + + final ConditionalSubscriber a = actual; + final Queue q = queue; + + long emitted = produced; + long polled = consumed; + + for (; ; ) { + + long r = requested; + + while (emitted != r) { + boolean d = done; + T v; + try { + v = q.poll(); + } + catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + s.cancel(); + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + q.clear(); + + doError(a, Operators.onOperatorError(ex, actual.currentContext())); + return; + } + boolean empty = v == null; + + if (checkTerminated(d, empty, a, v)) { + return; + } + + if (empty) { + break; + } + + if (a.tryOnNext(v)) { + emitted++; + } + + polled++; + + if (polled == limit) { + s.request(polled); + polled = 0L; + } + } + + if (emitted == r && checkTerminated(done, q.isEmpty(), a, null)) { + return; + } + + int w = wip; + if (missed == w) { + produced = emitted; + consumed = polled; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + else { + missed = w; + } + } + + } + + void runBackfused() { + 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(); + return; + } + + boolean d = done; + + actual.onNext(null); + + if (d) { + Throwable e = error; + if (e != null) { + doError(actual, e); + } + else { + doComplete(actual); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void run() { + if (outputFused) { + runBackfused(); + } + else if (sourceMode == Fuseable.SYNC) { + runSync(); + } + else { + runAsync(); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.TERMINATED) return done; + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.ERROR) return error; + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.RUN_ON) return worker; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + void doComplete(Subscriber a) { + a.onComplete(); + worker.dispose(); + } + + void doError(Subscriber a, Throwable e) { + try { + a.onError(e); + } + finally { + worker.dispose(); + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, @Nullable T v) { + if (cancelled) { + Operators.onDiscard(v, actual.currentContext()); + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } else { + // discard MUST be happening only and only if there is no racing on elements consumption + // which is guaranteed by the WIP guard here + Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); + } + return true; + } + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + doError(a, e); + } + else { + doComplete(a); + } + return true; + } + } + else { + Throwable e = error; + if (e != null) { + Operators.onDiscard(v, actual.currentContext()); + if (sourceMode == ASYNC) { + // delegates discarding to the queue holder to ensure there is no racing on draining from the SpScQueue + queue.clear(); + } else { + // discard MUST be happening only and only if there is no racing on elements consumption + // which is guaranteed by the WIP guard here + Operators.onDiscardQueueWithClear(queue, actual.currentContext(), null); + } + doError(a, e); + return true; + } + else if (empty) { + doComplete(a); + return true; + } + } + } + + return false; + } + + @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, actual.currentContext(), null); + + int dg = discardGuard; + if (missed == dg) { + missed = DISCARD_GUARD.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + else { + missed = dg; + } + } + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + @Nullable + public T poll() { + T v = queue.poll(); + if (v != null && sourceMode != SYNC) { + long p = consumed + 1; + if (p == limit) { + consumed = 0; + s.request(p); + } + else { + consumed = p; + } + } + return v; + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & ASYNC) != 0) { + outputFused = true; + return ASYNC; + } + return NONE; + } + + @Override + public int size() { + return queue.size(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxRange.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRange.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRange.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Emits a range of integer values. + * + * @see Reactive-Streams-Commons + */ +final class FluxRange extends Flux + implements Fuseable, SourceProducer { + + final long start; + + final long end; + + FluxRange(int start, int count) { + if (count < 0) { + throw new IllegalArgumentException("count >= required but it was " + count); + } + long e = (long) start + count; + if (e - 1 > Integer.MAX_VALUE) { + throw new IllegalArgumentException("start + count must be less than Integer.MAX_VALUE + 1"); + } + + this.start = start; + this.end = e; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + long st = start; + long en = end; + if (st == en) { + Operators.complete(actual); + return; + } else + if (st + 1 == en) { + actual.onSubscribe(Operators.scalarSubscription(actual, (int)st)); + return; + } + + if (actual instanceof ConditionalSubscriber) { + actual.onSubscribe(new RangeSubscriptionConditional((ConditionalSubscriber) actual, st, en)); + return; + } + actual.onSubscribe(new RangeSubscription(actual, st, en)); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class RangeSubscription implements InnerProducer, + SynchronousSubscription { + + final CoreSubscriber actual; + + final long end; + + volatile boolean cancelled; + + long index; + + volatile long requested; + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(RangeSubscription.class, "requested"); + + RangeSubscription(CoreSubscriber actual, long start, long end) { + this.actual = actual; + this.index = start; + this.end = end; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (Operators.addCap(REQUESTED, this, n) == 0) { + if (n == Long.MAX_VALUE) { + fastPath(); + } else { + slowPath(n); + } + } + } + } + + @Override + public void cancel() { + cancelled = true; + } + + void fastPath() { + final long e = end; + final Subscriber a = actual; + + for (long i = index; i != e; i++) { + if (cancelled) { + return; + } + + a.onNext((int) i); + } + + if (cancelled) { + return; + } + + a.onComplete(); + } + + void slowPath(long n) { + final Subscriber a = actual; + + long f = end; + long e = 0; + long i = index; + + for (; ; ) { + + if (cancelled) { + return; + } + + while (e != n && i != f) { + + a.onNext((int) i); + + if (cancelled) { + return; + } + + e++; + i++; + } + + if (cancelled) { + return; + } + + if (i == f) { + a.onComplete(); + return; + } + + n = requested; + if (n == e) { + index = i; + n = REQUESTED.addAndGet(this, -e); + if (n == 0) { + return; + } + e = 0; + } + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.TERMINATED) return isEmpty(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + @Nullable + public Integer poll() { + long i = index; + if (i == end) { + return null; + } + index = i + 1; + return (int)i; + } + + @Override + public boolean isEmpty() { + return index == end; + } + + @Override + public void clear() { + index = end; + } + + @Override + public int size() { + return (int)(end - index); + } + } + + static final class RangeSubscriptionConditional + implements InnerProducer, + SynchronousSubscription { + + final ConditionalSubscriber actual; + + final long end; + + volatile boolean cancelled; + + long index; + + volatile long requested; + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(RangeSubscriptionConditional.class, "requested"); + + RangeSubscriptionConditional(ConditionalSubscriber actual, + long start, + long end) { + this.actual = actual; + this.index = start; + this.end = end; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (Operators.addCap(REQUESTED, this, n) == 0) { + if (n == Long.MAX_VALUE) { + fastPath(); + } else { + slowPath(n); + } + } + } + } + + @Override + public void cancel() { + cancelled = true; + } + + void fastPath() { + final long e = end; + final ConditionalSubscriber a = actual; + + for (long i = index; i != e; i++) { + if (cancelled) { + return; + } + + a.tryOnNext((int) i); + } + + if (cancelled) { + return; + } + + a.onComplete(); + } + + void slowPath(long n) { + final ConditionalSubscriber a = actual; + + long f = end; + long e = 0; + long i = index; + + for (; ; ) { + + if (cancelled) { + return; + } + + while (e != n && i != f) { + + boolean b = a.tryOnNext((int) i); + + if (cancelled) { + return; + } + + if (b) { + e++; + } + i++; + } + + if (cancelled) { + return; + } + + if (i == f) { + a.onComplete(); + return; + } + + n = requested; + if (n == e) { + index = i; + n = REQUESTED.addAndGet(this, -e); + if (n == 0) { + return; + } + e = 0; + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.TERMINATED) return isEmpty(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + @Nullable + public Integer poll() { + long i = index; + if (i == end) { + return null; + } + index = i + 1; + return (int)i; + } + + @Override + public boolean isEmpty() { + return index == end; + } + + @Override + public void clear() { + index = end; + } + + @Override + public int size() { + return (int)(end - index); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCount.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCount.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCount.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Connects to the underlying Flux once the given number of Subscribers subscribed + * to it and disconnects once all Subscribers cancelled their Subscriptions. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxRefCount extends Flux implements Scannable, Fuseable { + + final ConnectableFlux source; + + final int n; + + @Nullable + RefCountMonitor connection; + + FluxRefCount(ConnectableFlux source, int n) { + if (n <= 0) { + throw new IllegalArgumentException("n > 0 required but it was " + n); + } + this.source = Objects.requireNonNull(source, "source"); + this.n = n; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public void subscribe(CoreSubscriber actual) { + RefCountMonitor conn; + + boolean connect = false; + synchronized (this) { + conn = connection; + if (conn == null || conn.terminated) { + conn = new RefCountMonitor<>(this); + connection = conn; + } + + long c = conn.subscribers; + conn.subscribers = c + 1; + if (!conn.connected && c + 1 == n) { + connect = true; + conn.connected = true; + } + } + + source.subscribe(new RefCountInner<>(actual, conn)); + + if (connect) { + source.connect(conn); + } + } + + void cancel(RefCountMonitor rc) { + Disposable dispose = null; + synchronized (this) { + if (rc.terminated) { + return; + } + long c = rc.subscribers - 1; + rc.subscribers = c; + if (c != 0L || !rc.connected) { + return; + } + if (rc == connection) { + dispose = RefCountMonitor.DISCONNECT.getAndSet(rc, Disposables.disposed()); + connection = null; + } + } + if (dispose != null) { + dispose.dispose(); + } + } + + void terminated(RefCountMonitor rc) { + synchronized (this) { + if (!rc.terminated) { + rc.terminated = true; + connection = null; + } + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + static final class RefCountMonitor implements Consumer { + + final FluxRefCount parent; + + long subscribers; + + boolean terminated; + boolean connected; + + volatile Disposable disconnect; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater DISCONNECT = + AtomicReferenceFieldUpdater.newUpdater(RefCountMonitor.class, Disposable.class, "disconnect"); + + RefCountMonitor(FluxRefCount parent) { + this.parent = parent; + } + + @Override + public void accept(Disposable r) { + OperatorDisposables.replace(DISCONNECT, this, r); + } + + void innerCancelled() { + parent.cancel(this); + } + + void upstreamFinished() { + parent.terminated(this); + } + } + + static final class RefCountInner + implements QueueSubscription, InnerOperator { + + final CoreSubscriber actual; + final 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"); + + RefCountInner(CoreSubscriber actual, RefCountMonitor connection) { + 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.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + + @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); + } + else { + Operators.onErrorDropped(t, actual.currentContext()); + } + } + + @Override + public void onComplete() { + if (PARENT_DONE.compareAndSet(this, 0, 1)) { + connection.upstreamFinished(); + actual.onComplete(); + } + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + if (PARENT_DONE.compareAndSet(this, 0, 2)) { + connection.innerCancelled(); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @SuppressWarnings("unchecked") + public int requestFusion(int requestedMode) { + if(s instanceof QueueSubscription){ + qs = (QueueSubscription)s; + return qs.requestFusion(requestedMode); + } + return Fuseable.NONE; + } + + @Override + @Nullable + public T poll() { + return qs.poll(); + } + + @Override + public int size() { + return qs.size(); + } + + @Override + public boolean isEmpty() { + return qs.isEmpty(); + } + + @Override + public void clear() { + qs.clear(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCountGrace.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCountGrace.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRefCountGrace.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2017-2021 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.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +/** + * @author Simon Baslé + * @author David Karnok + */ +//adapted from RxJava2Extensions: https://github.com/akarnokd/RxJava2Extensions/blob/master/src/main/java/hu/akarnokd/rxjava2/operators/FlowableRefCountTimeout.java +final class FluxRefCountGrace extends Flux implements Scannable, Fuseable { + + final ConnectableFlux source; + final int n; + final Duration gracePeriod; + final Scheduler scheduler; + + RefConnection connection; + + FluxRefCountGrace(ConnectableFlux source, int n, Duration gracePeriod, Scheduler scheduler) { + this.source = source; + this.n = n; + this.gracePeriod = gracePeriod; + this.scheduler = scheduler; + } + + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void subscribe(CoreSubscriber actual) { + RefConnection conn; + + boolean connect = false; + synchronized (this) { + conn = connection; + if (conn == null || conn.isTerminated()) { + conn = new RefConnection(this); + connection = conn; + } + + long c = conn.subscriberCount; + if (c == 0L && conn.timer != null) { + conn.timer.dispose(); + } + conn.subscriberCount = c + 1; + if (!conn.connected && c + 1 == n) { + connect = true; + conn.connected = true; + } + } + + source.subscribe(new RefCountInner<>(actual, this, conn)); + + if (connect) { + source.connect(conn); + } + } + + void cancel(RefConnection rc) { + boolean replaceTimer = false; + Disposable dispose = null; + Disposable.Swap sd = null; + synchronized (this) { + if (rc.terminated) { + return; + } + long c = rc.subscriberCount - 1; + rc.subscriberCount = c; + if (c != 0L || !rc.connected) { + return; + } + if (!gracePeriod.isZero()) { + sd = Disposables.swap(); + rc.timer = sd; + replaceTimer = true; + } + else if (rc == connection) { + //emulate what a timeout would do without getting out of sync block + //capture the disposable for later disposal + connection = null; + dispose = RefConnection.SOURCE_DISCONNECTOR.getAndSet(rc, Disposables.disposed()); + } + } + + if (replaceTimer) { + sd.replace(scheduler.schedule(rc, gracePeriod.toNanos(), TimeUnit.NANOSECONDS)); + } else if (dispose != null) { + dispose.dispose(); + } + } + + void terminated(RefConnection rc) { + synchronized (this) { + if (!rc.terminated) { + rc.terminated = true; + connection = null; + } + } + } + + void timeout(RefConnection rc) { + Disposable dispose = null; + synchronized (this) { + if (rc.subscriberCount == 0 && rc == connection) { + connection = null; + dispose = RefConnection.SOURCE_DISCONNECTOR.getAndSet(rc, Disposables.disposed()); + } + } + if (dispose != null) { + dispose.dispose(); + } + } + + static final class RefConnection implements Runnable, Consumer { + + final FluxRefCountGrace parent; + + Disposable timer; + long subscriberCount; + boolean connected; + boolean terminated; + + volatile Disposable sourceDisconnector; + static final AtomicReferenceFieldUpdater SOURCE_DISCONNECTOR = + AtomicReferenceFieldUpdater.newUpdater(RefConnection.class, Disposable.class, "sourceDisconnector"); + + RefConnection(FluxRefCountGrace parent) { + this.parent = parent; + } + + /** + * Indicates whether the RefConnection is terminated OR the source's connection has been disposed + * @return true if the connection can be considered as terminated, either by this operator or by the source + */ + boolean isTerminated() { + Disposable sd = sourceDisconnector; + return terminated || (sd != null && sd.isDisposed()); + } + + @Override + public void run() { + parent.timeout(this); + } + + @Override + public void accept(Disposable t) { + OperatorDisposables.replace(SOURCE_DISCONNECTOR, this, t); + } + } + + static final class RefCountInner implements QueueSubscription, InnerOperator { + + final CoreSubscriber actual; + + final FluxRefCountGrace parent; + + final RefConnection connection; + + Subscription s; + QueueSubscription qs; + + volatile int parentDone; + static final AtomicIntegerFieldUpdater PARENT_DONE = + AtomicIntegerFieldUpdater.newUpdater(RefCountInner.class, "parentDone"); + + RefCountInner(CoreSubscriber actual, FluxRefCountGrace parent, + RefConnection connection) { + this.actual = actual; + this.parent = parent; + this.connection = connection; + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (PARENT_DONE.compareAndSet(this, 0, 1)) { + parent.terminated(connection); + } + actual.onError(t); + } + + @Override + public void onComplete() { + if (PARENT_DONE.compareAndSet(this, 0, 1)) { + parent.terminated(connection); + } + actual.onComplete(); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + if (PARENT_DONE.compareAndSet(this, 0, 1)) { + parent.cancel(connection); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @Override + @SuppressWarnings("unchecked") + public int requestFusion(int requestedMode) { + if(s instanceof QueueSubscription){ + qs = (QueueSubscription)s; + return qs.requestFusion(requestedMode); + } + return Fuseable.NONE; + } + + @Override + @Nullable + public T poll() { + return qs.poll(); + } + + @Override + public int size() { + return qs.size(); + } + + @Override + public boolean isEmpty() { + return qs.isEmpty(); + } + + @Override + public void clear() { + qs.clear(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return InnerOperator.super.scanUnsafe(key); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxRepeat.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRepeat.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRepeat.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; + +/** + * Repeatedly subscribes to the source and relays its values either + * indefinitely or a fixed number of times. + *

+ * The times == Long.MAX_VALUE is treated as infinite repeat. Times = 1 mirrors the source + * (the "original" run) and then repeats it once more. Callers should take care of times = + * 0 and times = -1 cases, which should map to replaying the source and an empty sequence, + * respectively. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxRepeat extends InternalFluxOperator { + + final long times; + + FluxRepeat(Flux source, long times) { + super(source); + if (times <= 0L) { + throw new IllegalArgumentException("times > 0 required"); + } + this.times = times; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + RepeatSubscriber parent = new RepeatSubscriber<>(source, actual, times + 1); + + actual.onSubscribe(parent); + + if (!parent.isCancelled()) { + parent.onComplete(); + } + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class RepeatSubscriber + extends Operators.MultiSubscriptionSubscriber { + + final CorePublisher source; + + long remaining; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(RepeatSubscriber.class, "wip"); + + long produced; + + RepeatSubscriber(CorePublisher source, CoreSubscriber actual, long remaining) { + super(actual); + this.source = source; + this.remaining = remaining; + } + + @Override + public void onNext(T t) { + produced++; + + actual.onNext(t); + } + + @Override + public void onComplete() { + long r = remaining; + if (r != Long.MAX_VALUE) { + if (r == 0) { + actual.onComplete(); + return; + } + remaining = r - 1; + } + + resubscribe(); + } + + void resubscribe() { + if (WIP.getAndIncrement(this) == 0) { + do { + if (isCancelled()) { + return; + } + + long c = produced; + if (c != 0L) { + produced = 0L; + produced(c); + } + + source.subscribe(this); + + } while (WIP.decrementAndGet(this) != 0); + } + } + + @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/FluxRepeatPredicate.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRepeatPredicate.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRepeatPredicate.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.function.BooleanSupplier; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; + +/** + * Repeatedly subscribes to the source if the predicate returns true after + * completion of the previous subscription. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxRepeatPredicate extends InternalFluxOperator { + + final BooleanSupplier predicate; + + FluxRepeatPredicate(Flux source, BooleanSupplier predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + RepeatPredicateSubscriber parent = new RepeatPredicateSubscriber<>(source, + actual, predicate); + + actual.onSubscribe(parent); + + if (!parent.isCancelled()) { + parent.resubscribe(); + } + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class RepeatPredicateSubscriber + extends Operators.MultiSubscriptionSubscriber { + + final CorePublisher source; + + final BooleanSupplier predicate; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(RepeatPredicateSubscriber.class, "wip"); + + long produced; + + RepeatPredicateSubscriber(CorePublisher source, + CoreSubscriber actual, BooleanSupplier predicate) { + super(actual); + this.source = source; + this.predicate = predicate; + } + + @Override + public void onNext(T t) { + produced++; + + actual.onNext(t); + } + + @Override + public void onComplete() { + boolean b; + + try { + b = predicate.getAsBoolean(); + } catch (Throwable e) { + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + return; + } + + if (b) { + resubscribe(); + } else { + actual.onComplete(); + } + } + + void resubscribe() { + if (WIP.getAndIncrement(this) == 0) { + do { + if (isCancelled()) { + return; + } + + long c = produced; + if (c != 0L) { + produced = 0L; + produced(c); + } + + source.subscribe(this); + + } while (WIP.decrementAndGet(this) != 0); + } + } + + @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/FluxRepeatWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRepeatWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRepeatWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.function.Function; +import java.util.stream.Stream; + +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 reactor.util.context.ContextView; + +/** + * Repeats a source when a companion sequence signals an item in response to the main's + * completion signal + *

+ *

If the companion sequence signals when the main source is active, the repeat attempt + * is suppressed and any terminal signal will terminate the main source with the same + * signal immediately. + * + * @param the source value type + * + * @see Reactive-Streams-Commons + */ +final class FluxRepeatWhen extends InternalFluxOperator { + + final Function, ? extends Publisher> whenSourceFactory; + + FluxRepeatWhen(Flux source, + Function, ? extends Publisher> whenSourceFactory) { + super(source); + this.whenSourceFactory = + Objects.requireNonNull(whenSourceFactory, "whenSourceFactory"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + RepeatWhenOtherSubscriber other = new RepeatWhenOtherSubscriber(); + CoreSubscriber serial = Operators.serialize(actual); + + RepeatWhenMainSubscriber main = + new RepeatWhenMainSubscriber<>(serial, other.completionSignal, source); + other.main = main; + + serial.onSubscribe(main); + + Publisher p; + + try { + p = Objects.requireNonNull(whenSourceFactory.apply(other), + "The whenSourceFactory returned a null Publisher"); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + return null; + } + + p.subscribe(other); + + if (!main.cancelled) { + return main; + } + else { + return null; + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class RepeatWhenMainSubscriber + extends Operators.MultiSubscriptionSubscriber { + + final Operators.DeferredSubscription otherArbiter; + + final Sinks.Many signaller; + + final CorePublisher source; + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(RepeatWhenMainSubscriber.class, + "wip"); + + Context context; + long produced; + + RepeatWhenMainSubscriber(CoreSubscriber actual, + Sinks.Many signaller, + CorePublisher source) { + super(actual); + this.signaller = signaller; + this.source = source; + this.otherArbiter = new Operators.DeferredSubscription(); + this.context = actual.currentContext(); + } + + @Override + public Context currentContext() { + return this.context; + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(signaller), otherArbiter); + } + + @Override + public void cancel() { + if (!cancelled) { + otherArbiter.cancel(); + super.cancel(); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + + produced++; + } + + @Override + public void onError(Throwable t) { + otherArbiter.cancel(); + + actual.onError(t); + } + + @Override + public void onComplete() { + long p = produced; + if (p != 0L) { + produced = 0; + produced(p); + } + + signaller.emitNext(p, Sinks.EmitFailureHandler.FAIL_FAST); + // request after signalling, otherwise it may race + otherArbiter.request(1); + } + + void setWhen(Subscription w) { + otherArbiter.set(w); + } + + void resubscribe(Object trigger) { + if (WIP.getAndIncrement(this) == 0) { + do { + if (cancelled) { + return; + } + + //flow that emit a Context as a trigger for the re-subscription are + //used to REPLACE the currentContext() + if (trigger instanceof ContextView) { + this.context = this.context.putAll((ContextView) trigger); + } + + source.subscribe(this); + + } + while (WIP.decrementAndGet(this) != 0); + } + } + + void whenError(Throwable e) { + super.cancel(); + + actual.onError(e); + } + + void whenComplete() { + super.cancel(); + + actual.onComplete(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + } + + static final class RepeatWhenOtherSubscriber extends Flux + implements InnerConsumer, OptimizableOperator { + + RepeatWhenMainSubscriber main; + + final Sinks.Many completionSignal = Sinks.many().multicast().onBackpressureBuffer(); + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return main.otherArbiter; + if (key == Attr.ACTUAL) return main; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + main.setWhen(s); + } + + @Override + public void onNext(Object t) { + main.resubscribe(t); + } + + @Override + public void onError(Throwable t) { + main.whenError(t); + } + + @Override + public void onComplete() { + main.whenComplete(); + } + + @Override + public void subscribe(CoreSubscriber actual) { + completionSignal.asFlux().subscribe(actual); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public Flux source() { + return completionSignal.asFlux(); + } + + @Override + public OptimizableOperator nextOptimizableSource() { + return null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxReplay.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxReplay.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxReplay.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,1843 @@ +/* + * Copyright (c) 2016-2021 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.CancellationException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * @param + * @see Reactive-Streams-Commons + */ +final class FluxReplay extends ConnectableFlux + implements Scannable, Fuseable, OptimizableOperator { + + final CorePublisher source; + final int history; + final long ttl; + final Scheduler scheduler; + + volatile ReplaySubscriber connection; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater CONNECTION = + AtomicReferenceFieldUpdater.newUpdater(FluxReplay.class, + ReplaySubscriber.class, + "connection"); + + @Nullable + final OptimizableOperator optimizableOperator; + + interface ReplaySubscription extends QueueSubscription, InnerProducer { + + @Override + CoreSubscriber actual(); + + boolean enter(); + + int leave(int missed); + + void produced(long n); + + void node(@Nullable Object node); + + @Nullable + Object node(); + + int tailIndex(); + + void tailIndex(int tailIndex); + + int index(); + + void index(int index); + + int fusionMode(); + + boolean isCancelled(); + + long requested(); + + void requestMore(int index); + } + + interface ReplayBuffer { + + void add(T value); + + void onError(Throwable ex); + + @Nullable + Throwable getError(); + + void onComplete(); + + void replay(ReplaySubscription rs); + + boolean isDone(); + + @Nullable + T poll(ReplaySubscription rs); + + void clear(ReplaySubscription rs); + + boolean isEmpty(ReplaySubscription rs); + + int size(ReplaySubscription rs); + + int size(); + + int capacity(); + + boolean isExpired(); + } + + static final class SizeAndTimeBoundReplayBuffer implements ReplayBuffer { + + static final class TimedNode extends AtomicReference> { + + final int index; + final T value; + final long time; + + TimedNode(int index, @Nullable T value, long time) { + this.index = index; + this.value = value; + this.time = time; + } + } + + final int limit; + final int indexUpdateLimit; + final long maxAge; + final Scheduler scheduler; + int size; + + volatile TimedNode head; + + TimedNode tail; + + Throwable error; + static final long NOT_DONE = Long.MIN_VALUE; + + volatile long done = NOT_DONE; + + SizeAndTimeBoundReplayBuffer(int limit, + long maxAge, + Scheduler scheduler) { + this.limit = limit; + this.indexUpdateLimit = Operators.unboundedOrLimit(limit); + this.maxAge = maxAge; + this.scheduler = scheduler; + TimedNode h = new TimedNode<>(-1, null, 0L); + this.tail = h; + this.head = h; + } + + @Override + public boolean isExpired() { + long done = this.done; + return done != NOT_DONE && scheduler.now(TimeUnit.NANOSECONDS) - maxAge > done; + } + + @SuppressWarnings("unchecked") + void replayNormal(ReplaySubscription rs) { + int missed = 1; + final Subscriber a = rs.actual(); + + for (; ; ) { + @SuppressWarnings("unchecked") TimedNode node = + (TimedNode) rs.node(); + if (node == null) { + node = head; + if (done == NOT_DONE) { + // skip old entries + long limit = scheduler.now(TimeUnit.NANOSECONDS) - maxAge; + TimedNode next = node; + while (next != null) { + long ts = next.time; + if (ts > limit) { + break; + } + node = next; + next = node.get(); + } + } + } + + long r = rs.requested(); + long e = 0L; + + while (e != r) { + if (rs.isCancelled()) { + rs.node(null); + return; + } + + boolean d = done != NOT_DONE; + TimedNode next = node.get(); + boolean empty = next == null; + + if (d && empty) { + rs.node(null); + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(next.value); + + e++; + node = next; + + if ((next.index + 1) % indexUpdateLimit == 0) { + rs.requestMore(next.index + 1); + } + } + + if (e == r) { + if (rs.isCancelled()) { + rs.node(null); + return; + } + + boolean d = done != NOT_DONE; + boolean empty = node.get() == null; + + if (d && empty) { + rs.node(null); + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + } + + if (e != 0L) { + if (r != Long.MAX_VALUE) { + rs.produced(e); + } + } + + rs.node(node); + + missed = rs.leave(missed); + if (missed == 0) { + break; + } + } + } + + void replayFused(ReplaySubscription rs) { + int missed = 1; + + final Subscriber a = rs.actual(); + + for (; ; ) { + + if (rs.isCancelled()) { + rs.node(null); + return; + } + + boolean d = done != NOT_DONE; + + a.onNext(null); + + if (d) { + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + missed = rs.leave(missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void onError(Throwable ex) { + done = scheduler.now(TimeUnit.NANOSECONDS); + error = ex; + } + + @Override + @Nullable + public Throwable getError() { + return error; + } + + @Override + public void onComplete() { + done = scheduler.now(TimeUnit.NANOSECONDS); + } + + @Override + public boolean isDone() { + return done != NOT_DONE; + } + + @SuppressWarnings("unchecked") + TimedNode latestHead(ReplaySubscription rs) { + long now = scheduler.now(TimeUnit.NANOSECONDS) - maxAge; + + TimedNode h = (TimedNode) rs.node(); + if (h == null) { + h = head; + } + TimedNode n; + while ((n = h.get()) != null) { + if (n.time > now) { + break; + } + h = n; + } + return h; + } + + @Override + @Nullable + public T poll(ReplaySubscription rs) { + TimedNode node = latestHead(rs); + TimedNode next; + long now = scheduler.now(TimeUnit.NANOSECONDS) - maxAge; + while ((next = node.get()) != null) { + if (next.time > now) { + node = next; + break; + } + node = next; + } + if (next == null) { + if (node.index != -1 && (node.index + 1) % indexUpdateLimit == 0) { + rs.requestMore(node.index + 1); + } + return null; + } + rs.node(next); + if ((next.index + 1) % indexUpdateLimit == 0) { + rs.requestMore(next.index + 1); + } + + return node.value; + } + + @Override + public void clear(ReplaySubscription rs) { + rs.node(null); + } + + @Override + @SuppressWarnings("unchecked") + public boolean isEmpty(ReplaySubscription rs) { + TimedNode node = latestHead(rs); + return node.get() == null; + } + + @Override + public int size(ReplaySubscription rs) { + TimedNode node = latestHead(rs); + int count = 0; + + TimedNode next; + while ((next = node.get()) != null && count != Integer.MAX_VALUE) { + count++; + node = next; + } + + return count; + } + + @Override + public int size() { + TimedNode node = head; + int count = 0; + + TimedNode next; + while ((next = node.get()) != null && count != Integer.MAX_VALUE) { + count++; + node = next; + } + + return count; + } + + @Override + public int capacity() { + return limit; + } + + @Override + public void add(T value) { + final TimedNode tail = this.tail; + final TimedNode n = new TimedNode<>(tail.index + 1, + value, + scheduler.now(TimeUnit.NANOSECONDS)); + tail.set(n); + this.tail = n; + int s = size; + if (s == limit) { + head = head.get(); + } + else { + size = s + 1; + } + long limit = scheduler.now(TimeUnit.NANOSECONDS) - maxAge; + + TimedNode h = head; + TimedNode next; + int removed = 0; + for (; ; ) { + next = h.get(); + if (next == null) { + break; + } + + if (next.time > limit) { + if (removed != 0) { + size = size - removed; + head = h; + } + break; + } + + h = next; + removed++; + } + } + + @Override + @SuppressWarnings("unchecked") + public void replay(ReplaySubscription rs) { + if (!rs.enter()) { + return; + } + + if (rs.fusionMode() == NONE) { + replayNormal(rs); + } + else { + replayFused(rs); + } + } + } + + static final class UnboundedReplayBuffer implements ReplayBuffer { + + final int batchSize; + final int indexUpdateLimit; + + volatile int size; + + final Object[] head; + + Object[] tail; + + int tailIndex; + + volatile boolean done; + Throwable error; + + UnboundedReplayBuffer(int batchSize) { + this.batchSize = batchSize; + this.indexUpdateLimit = Operators.unboundedOrLimit(batchSize); + Object[] n = new Object[batchSize + 1]; + this.tail = n; + this.head = n; + } + + @Override + public boolean isExpired() { + return false; + } + + @Override + @Nullable + public Throwable getError() { + return error; + } + + @Override + public int capacity() { + return Integer.MAX_VALUE; + } + + @Override + public void add(T value) { + int i = tailIndex; + Object[] a = tail; + if (i == a.length - 1) { + Object[] b = new Object[a.length]; + b[0] = value; + tailIndex = 1; + a[i] = b; + tail = b; + } + else { + a[i] = value; + tailIndex = i + 1; + } + size++; + } + + @Override + public void onError(Throwable ex) { + error = ex; + done = true; + } + + @Override + public void onComplete() { + done = true; + } + + void replayNormal(ReplaySubscription rs) { + int missed = 1; + + final Subscriber a = rs.actual(); + final int n = batchSize; + + for (; ; ) { + + long r = rs.requested(); + long e = 0L; + + Object[] node = (Object[]) rs.node(); + if (node == null) { + node = head; + } + int tailIndex = rs.tailIndex(); + int index = rs.index(); + + while (e != r) { + if (rs.isCancelled()) { + rs.node(null); + return; + } + + boolean d = done; + boolean empty = index == size; + + if (d && empty) { + rs.node(null); + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + if (empty) { + break; + } + + if (tailIndex == n) { + node = (Object[]) node[tailIndex]; + tailIndex = 0; + } + + @SuppressWarnings("unchecked") T v = (T) node[tailIndex]; + + a.onNext(v); + + e++; + tailIndex++; + index++; + + if (index % indexUpdateLimit == 0) { + rs.requestMore(index); + } + } + + if (e == r) { + if (rs.isCancelled()) { + rs.node(null); + return; + } + + boolean d = done; + boolean empty = index == size; + + if (d && empty) { + rs.node(null); + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + } + + if (e != 0L) { + if (r != Long.MAX_VALUE) { + rs.produced(e); + } + } + + rs.index(index); + rs.tailIndex(tailIndex); + rs.node(node); + + missed = rs.leave(missed); + if (missed == 0) { + break; + } + } + } + + void replayFused(ReplaySubscription rs) { + int missed = 1; + + final Subscriber a = rs.actual(); + + for (; ; ) { + + if (rs.isCancelled()) { + rs.node(null); + return; + } + + boolean d = done; + + a.onNext(null); + + if (d) { + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + missed = rs.leave(missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void replay(ReplaySubscription rs) { + if (!rs.enter()) { + return; + } + + if (rs.fusionMode() == NONE) { + replayNormal(rs); + } + else { + replayFused(rs); + } + } + + @Override + public boolean isDone() { + return done; + } + + @Override + @Nullable + public T poll(ReplaySubscription rs) { + int index = rs.index(); + if (index == size) { + return null; + } + Object[] node = (Object[]) rs.node(); + if (node == null) { + node = head; + rs.node(node); + } + int tailIndex = rs.tailIndex(); + if (tailIndex == batchSize) { + node = (Object[]) node[tailIndex]; + tailIndex = 0; + rs.node(node); + } + @SuppressWarnings("unchecked") T v = (T) node[tailIndex]; + rs.tailIndex(tailIndex + 1); + + if ((index + 1) % indexUpdateLimit == 0) { + rs.requestMore(index + 1); + } + else { + rs.index(index + 1); + } + + return v; + } + + @Override + public void clear(ReplaySubscription rs) { + rs.node(null); + } + + @Override + public boolean isEmpty(ReplaySubscription rs) { + return rs.index() == size; + } + + @Override + public int size(ReplaySubscription rs) { + return size - rs.index(); + } + + @Override + public int size() { + return size; + } + + } + + static final class SizeBoundReplayBuffer implements ReplayBuffer { + + final int limit; + final int indexUpdateLimit; + + volatile Node head; + + Node tail; + + int size; + + volatile boolean done; + Throwable error; + + SizeBoundReplayBuffer(int limit) { + if (limit < 0) { + throw new IllegalArgumentException("Limit cannot be negative"); + } + this.limit = limit; + this.indexUpdateLimit = Operators.unboundedOrLimit(limit); + + Node n = new Node<>(-1, null); + this.tail = n; + this.head = n; + } + + @Override + public boolean isExpired() { + return false; + } + + @Override + public int capacity() { + return limit; + } + + @Override + public void add(T value) { + final Node tail = this.tail; + final Node n = new Node<>(tail.index + 1, value); + tail.set(n); + this.tail = n; + int s = size; + if (s == limit) { + head = head.get(); + } + else { + size = s + 1; + } + } + + @Override + public void onError(Throwable ex) { + error = ex; + done = true; + } + + @Override + public void onComplete() { + done = true; + } + + void replayNormal(ReplaySubscription rs) { + final Subscriber a = rs.actual(); + + int missed = 1; + + for (; ; ) { + + long r = rs.requested(); + long e = 0L; + + @SuppressWarnings("unchecked") Node node = (Node) rs.node(); + if (node == null) { + node = head; + } + + while (e != r) { + if (rs.isCancelled()) { + rs.node(null); + return; + } + + boolean d = done; + Node next = node.get(); + boolean empty = next == null; + + if (d && empty) { + rs.node(null); + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(next.value); + + e++; + node = next; + + if ((next.index + 1) % indexUpdateLimit == 0) { + rs.requestMore(next.index + 1); + } + } + + if (e == r) { + if (rs.isCancelled()) { + rs.node(null); + return; + } + + boolean d = done; + boolean empty = node.get() == null; + + if (d && empty) { + rs.node(null); + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + } + + if (e != 0L) { + if (r != Long.MAX_VALUE) { + rs.produced(e); + } + } + + rs.node(node); + + missed = rs.leave(missed); + if (missed == 0) { + break; + } + } + } + + void replayFused(ReplaySubscription rs) { + int missed = 1; + + final Subscriber a = rs.actual(); + + for (; ; ) { + + if (rs.isCancelled()) { + rs.node(null); + return; + } + + boolean d = done; + + a.onNext(null); + + if (d) { + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + missed = rs.leave(missed); + if (missed == 0) { + break; + } + } + } + + @Override + public void replay(ReplaySubscription rs) { + if (!rs.enter()) { + return; + } + + if (rs.fusionMode() == NONE) { + replayNormal(rs); + } + else { + replayFused(rs); + } + } + + @Override + @Nullable + public Throwable getError() { + return error; + } + + @Override + public boolean isDone() { + return done; + } + + static final class Node extends AtomicReference> { + + /** */ + private static final long serialVersionUID = 3713592843205853725L; + + final int index; + final T value; + + Node(int index, @Nullable T value) { + this.index = index; + this.value = value; + } + + @Override + public String toString() { + return "Node(" + value + ")"; + } + } + + @Override + @Nullable + public T poll(ReplaySubscription rs) { + @SuppressWarnings("unchecked") Node node = (Node) rs.node(); + if (node == null) { + node = head; + rs.node(node); + } + + Node next = node.get(); + if (next == null) { + return null; + } + rs.node(next); + + if ((next.index + 1) % indexUpdateLimit == 0) { + rs.requestMore(next.index + 1); + } + + return next.value; + } + + @Override + public void clear(ReplaySubscription rs) { + rs.node(null); + } + + @Override + public boolean isEmpty(ReplaySubscription rs) { + @SuppressWarnings("unchecked") Node node = (Node) rs.node(); + if (node == null) { + node = head; + rs.node(node); + } + return node.get() == null; + } + + @Override + public int size(ReplaySubscription rs) { + @SuppressWarnings("unchecked") Node node = (Node) rs.node(); + if (node == null) { + node = head; + } + int count = 0; + + Node next; + while ((next = node.get()) != null && count != Integer.MAX_VALUE) { + count++; + node = next; + } + + return count; + } + + @Override + public int size() { + Node node = head; + int count = 0; + + Node next; + while ((next = node.get()) != null && count != Integer.MAX_VALUE) { + count++; + node = next; + } + + return count; + } + } + + FluxReplay(CorePublisher source, + int history, + long ttl, + @Nullable Scheduler scheduler) { + this.source = Objects.requireNonNull(source, "source"); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + + if (history <= 0) { + throw new IllegalArgumentException("History cannot be zero or negative : " + history); + } + + this.history = history; + + if (scheduler != null && ttl < 0) { + throw new IllegalArgumentException("TTL cannot be negative : " + ttl); + } + this.ttl = ttl; + this.scheduler = scheduler; + } + + @Override + public int getPrefetch() { + return history; + } + + ReplaySubscriber newState() { + if (scheduler != null) { + return new ReplaySubscriber<>(new SizeAndTimeBoundReplayBuffer<>(history, + ttl, + scheduler), this, history); + } + if (history != Integer.MAX_VALUE) { + return new ReplaySubscriber<>(new SizeBoundReplayBuffer<>(history), + this, + history); + } + return new ReplaySubscriber<>(new UnboundedReplayBuffer<>(Queues.SMALL_BUFFER_SIZE), + this, + Queues.SMALL_BUFFER_SIZE); + } + + @Override + public void connect(Consumer cancelSupport) { + boolean doConnect; + ReplaySubscriber s; + for (; ; ) { + s = connection; + if (s == null) { + ReplaySubscriber u = newState(); + if (!CONNECTION.compareAndSet(this, null, u)) { + continue; + } + + s = u; + } + + doConnect = s.tryConnect(); + break; + } + + cancelSupport.accept(s); + if (doConnect) { + try { + source.subscribe(s); + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(s, e); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + try { + CoreSubscriber nextSubscriber = subscribeOrReturn(actual); + if (nextSubscriber == null) { + return; + } + source.subscribe(nextSubscriber); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + } + } + + @Override + public final CoreSubscriber subscribeOrReturn(CoreSubscriber actual) + throws Throwable { + boolean expired; + for (; ; ) { + ReplaySubscriber c = connection; + expired = scheduler != null && c != null && c.buffer.isExpired(); + if (c == null || expired) { + ReplaySubscriber u = newState(); + if (!CONNECTION.compareAndSet(this, c, u)) { + continue; + } + + c = u; + } + + ReplayInner inner = new ReplayInner<>(actual, c); + actual.onSubscribe(inner); + c.add(inner); + + if (inner.isCancelled()) { + c.remove(inner); + return null; + } + + c.buffer.replay(inner); + + if (expired) { + return c; + } + + break; + } + return null; + } + + @Override + public final CorePublisher source() { + return source; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + @Nullable + public Object scanUnsafe(Scannable.Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + static final class ReplaySubscriber implements InnerConsumer, Disposable { + + final FluxReplay parent; + final ReplayBuffer buffer; + final long prefetch; + final int limit; + + Subscription s; + int produced; + int nextPrefetchIndex; + + volatile ReplaySubscription[] subscribers; + + volatile long state; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater STATE = + AtomicLongFieldUpdater.newUpdater(ReplaySubscriber.class, "state"); + + @SuppressWarnings("rawtypes") + static final ReplaySubscription[] EMPTY = new ReplaySubscription[0]; + @SuppressWarnings("rawtypes") + static final ReplaySubscription[] TERMINATED = new ReplaySubscription[0]; + + @SuppressWarnings("unchecked") + ReplaySubscriber(ReplayBuffer buffer, FluxReplay parent, int prefetch) { + this.buffer = buffer; + this.parent = parent; + this.subscribers = EMPTY; + this.prefetch = Operators.unboundedOrPrefetch(prefetch); + this.limit = Operators.unboundedOrLimit(prefetch); + this.nextPrefetchIndex = this.limit; + } + + @Override + public void onSubscribe(Subscription s) { + if (buffer.isDone()) { + s.cancel(); + return; + } + + if (Operators.validate(this.s, s)) { + this.s = s; + final long previousState = markSubscribed(this); + + if (isDisposed(previousState)) { + s.cancel(); + return; + } + + s.request(this.prefetch); + } + } + + void manageRequest(long currentState) { + final Subscription p = this.s; + for (; ; ) { + + int nextPrefetchIndex = this.nextPrefetchIndex; + boolean shouldPrefetch; + + // find out if we need to make another prefetch + final ReplaySubscription[] subscribers = this.subscribers; + if (subscribers.length > 0) { + shouldPrefetch = true; + for (ReplaySubscription rp : subscribers) { + if (rp.index() < nextPrefetchIndex) { + shouldPrefetch = false; + break; + } + } + } + else { + shouldPrefetch = this.produced >= nextPrefetchIndex; + } + + if (shouldPrefetch) { + final int limit = this.limit; + this.nextPrefetchIndex = nextPrefetchIndex + limit; + p.request(limit); + } + + currentState = markWorkDone(this, currentState); + // if the upstream has completed, no more requesting is possible + if (isDisposed(currentState)) { + return; + } + + if (!isWorkInProgress(currentState)) { + return; + } + } + } + + @Override + public void onNext(T t) { + ReplayBuffer b = buffer; + if (b.isDone()) { + Operators.onNextDropped(t, currentContext()); + return; + } + + produced++; + + b.add(t); + + final ReplaySubscription[] subscribers = this.subscribers; + if (subscribers.length == 0) { + if (produced % limit == 0) { + final long previousState = markWorkAdded(this); + if (isDisposed(previousState)) { + return; + } + + if (isWorkInProgress(previousState)) { + return; + } + + manageRequest(previousState + 1); + } + return; + } + + for (ReplaySubscription rs : subscribers) { + b.replay(rs); + } + + + } + + @Override + public void onError(Throwable t) { + ReplayBuffer b = buffer; + if (b.isDone()) { + Operators.onErrorDropped(t, currentContext()); + } + else { + b.onError(t); + + for (ReplaySubscription rs : terminate()) { + b.replay(rs); + } + } + } + + @Override + public void onComplete() { + ReplayBuffer b = buffer; + if (!b.isDone()) { + b.onComplete(); + + for (ReplaySubscription rs : terminate()) { + b.replay(rs); + } + } + } + + @Override + public void dispose() { + final long previousState = markDisposed(this); + if (isDisposed(previousState)) { + return; + } + + if (isSubscribed(previousState)) { + s.cancel(); + } + + CONNECTION.lazySet(parent, null); + + final CancellationException ex = new CancellationException("Disconnected"); + final ReplayBuffer buffer = this.buffer; + buffer.onError(ex); + + for (ReplaySubscription inner : terminate()) { + buffer.replay(inner); + } + } + + boolean add(ReplayInner inner) { + if (subscribers == TERMINATED) { + return false; + } + synchronized (this) { + ReplaySubscription[] a = subscribers; + if (a == TERMINATED) { + return false; + } + int n = a.length; + + @SuppressWarnings("unchecked") ReplayInner[] b = + new ReplayInner[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + + subscribers = b; + return true; + } + } + + @SuppressWarnings("unchecked") + void remove(ReplaySubscription inner) { + ReplaySubscription[] a = subscribers; + if (a == TERMINATED || a == EMPTY) { + return; + } + synchronized (this) { + a = subscribers; + if (a == TERMINATED || a == EMPTY) { + return; + } + + int j = -1; + int n = a.length; + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + if (j < 0) { + return; + } + + ReplaySubscription[] b; + if (n == 1) { + b = EMPTY; + } + else { + b = new ReplayInner[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + + subscribers = b; + } + } + + @SuppressWarnings("unchecked") + ReplaySubscription[] terminate() { + ReplaySubscription[] a = subscribers; + if (a == TERMINATED) { + return a; + } + synchronized (this) { + a = subscribers; + if (a != TERMINATED) { + subscribers = TERMINATED; + } + return a; + } + } + + boolean isTerminated() { + return subscribers == TERMINATED; + } + + boolean tryConnect() { + return markConnected(this); + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.CAPACITY) return buffer.capacity(); + if (key == Attr.ERROR) return buffer.getError(); + if (key == Attr.BUFFERED) return buffer.size(); + if (key == Attr.TERMINATED) return isTerminated(); + if (key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public boolean isDisposed() { + return isDisposed(this.state); + } + + static final long CONNECTED_FLAG = + 0b0001_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long SUBSCRIBED_FLAG = + 0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long DISPOSED_FLAG = + 0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L; + static final long WORK_IN_PROGRESS_MAX_VALUE = + 0b0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111L; + + /** + * Adds {@link #CONNECTED_FLAG} to the state. Fails if the flag is already set + * + * @param instance to operate on + * @return true if flag was set + */ + static boolean markConnected(ReplaySubscriber instance) { + for (; ; ) { + final long state = instance.state; + + if (isConnected(state)) { + return false; + } + + if (STATE.compareAndSet(instance, state, state | CONNECTED_FLAG)) { + return true; + } + } + } + + /** + * Adds {@link #SUBSCRIBED_FLAG} to the state. Fails if states has the flag {@link + * #DISPOSED_FLAG} + * + * @param instance to operate on + * @return previous observed state + */ + static long markSubscribed(ReplaySubscriber instance) { + for (; ; ) { + final long state = instance.state; + + if (isDisposed(state)) { + return state; + } + + if (STATE.compareAndSet(instance, state, state | SUBSCRIBED_FLAG)) { + return state; + } + } + } + + /** + * Increments the work in progress part of the state, up to its max value. Fails + * if states has already had the {@link #DISPOSED_FLAG} flag + * + * @param instance to operate on + * @return previous observed state + */ + static long markWorkAdded(ReplaySubscriber instance) { + for (; ; ) { + final long state = instance.state; + + if (isDisposed(state)) { + return state; + } + + if ((state & WORK_IN_PROGRESS_MAX_VALUE) == WORK_IN_PROGRESS_MAX_VALUE) { + return state; + } + + if (STATE.compareAndSet(instance, state, state + 1)) { + return state; + } + } + } + + /** + * Sets work in progress to zero. Fails if given states not equal to the actual + * state. + * + * @param instance to operate on + * @return previous observed state + */ + static long markWorkDone(ReplaySubscriber instance, long currentState) { + for (; ; ) { + final long state = instance.state; + + if (currentState != state) { + return state; + } + + final long nextState = state & ~WORK_IN_PROGRESS_MAX_VALUE; + if (STATE.compareAndSet(instance, state, nextState)) { + return nextState; + } + } + } + + /** + * Adds {@link #DISPOSED_FLAG} to the state. Fails if states has already had + * the flag + * + * @param instance to operate on + * @return previous observed state + */ + static long markDisposed(ReplaySubscriber instance) { + for (; ; ) { + final long state = instance.state; + + if (isDisposed(state)) { + return state; + } + + if (STATE.compareAndSet(instance, state, state | DISPOSED_FLAG)) { + return state; + } + } + } + + /** + * Check if state has {@link #CONNECTED_FLAG} flag indicating that the + * {@link #connect(Consumer)} method was called and we have already connected + * to the upstream + * + * @param state to check flag presence + * @return true if flag is set + */ + static boolean isConnected(long state) { + return (state & CONNECTED_FLAG) == CONNECTED_FLAG; + } + + /** + * Check if state has {@link #SUBSCRIBED_FLAG} flag indicating subscription + * reception from the upstream + * + * @param state to check flag presence + * @return true if flag is set + */ + static boolean isSubscribed(long state) { + return (state & SUBSCRIBED_FLAG) == SUBSCRIBED_FLAG; + } + + /** + * Check if states has bits indicating work in progress + * + * @param state to check there is any amount of work in progress + * @return true if there is work in progress + */ + static boolean isWorkInProgress(long state) { + return (state & WORK_IN_PROGRESS_MAX_VALUE) > 0; + } + + /** + * Check if state has {@link #DISPOSED_FLAG} flag + * + * @param state to check flag presence + * @return true if flag is set + */ + static boolean isDisposed(long state) { + return (state & DISPOSED_FLAG) == DISPOSED_FLAG; + } + + } + + static final class ReplayInner implements ReplaySubscription { + + final CoreSubscriber actual; + final ReplaySubscriber parent; + + int index; + + int tailIndex; + + Object node; + + int fusionMode; + + long totalRequested; + + 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"); + + ReplayInner(CoreSubscriber actual, ReplaySubscriber parent) { + this.actual = actual; + this.parent = parent; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (Operators.addCapCancellable(REQUESTED, this, n) != Long.MIN_VALUE) { + // assuming no race between subscriptions#request + totalRequested = Operators.addCap(totalRequested, n); + + parent.buffer.replay(this); + } + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return parent; + } + if (key == Attr.TERMINATED) { + return parent.isTerminated(); + } + if (key == Attr.BUFFERED) { + return size(); + } + if (key == Attr.CANCELLED) { + return isCancelled(); + } + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) { + return Math.max(0L, requested); + } + if (key == Attr.RUN_ON) { + return parent.parent.scheduler; + } + + return ReplaySubscription.super.scanUnsafe(key); + } + + @Override + public void cancel() { + if (REQUESTED.getAndSet(this, Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + if (enter()) { + node = null; + } + } + } + + @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 parent.buffer.poll(this); + } + + @Override + public void clear() { + parent.buffer.clear(this); + } + + @Override + public boolean isEmpty() { + return parent.buffer.isEmpty(this); + } + + @Override + public int size() { + return parent.buffer.size(this); + } + + @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 void requestMore(int index) { + this.index = index; + + final long previousState = ReplaySubscriber.markWorkAdded(this.parent); + + if (ReplaySubscriber.isDisposed(previousState)) { + return; + } + + if (ReplaySubscriber.isWorkInProgress(previousState)) { + return; + } + + this.parent.manageRequest(previousState + 1); + } + + @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); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxRetry.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRetry.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRetry.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; + +/** + * Repeatedly subscribes to the source sequence if it signals any error + * either indefinitely or a fixed number of times. + *

+ * The times == Long.MAX_VALUE is treated as infinite retry. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxRetry extends InternalFluxOperator { + + final long times; + + FluxRetry(Flux source, long times) { + super(source); + if (times < 0L) { + throw new IllegalArgumentException("times >= 0 required but it was " + times); + } + this.times = times; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + RetrySubscriber parent = new RetrySubscriber<>(source, actual, times); + + actual.onSubscribe(parent); + + if (!parent.isCancelled()) { + parent.resubscribe(); + } + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class RetrySubscriber + extends Operators.MultiSubscriptionSubscriber { + + final CorePublisher source; + + long remaining; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(RetrySubscriber.class, "wip"); + + long produced; + + RetrySubscriber(CorePublisher source, CoreSubscriber actual, long remaining) { + super(actual); + this.source = source; + this.remaining = remaining; + } + + @Override + public void onNext(T t) { + produced++; + + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + long r = remaining; + if (r != Long.MAX_VALUE) { + if (r == 0) { + actual.onError(t); + return; + } + remaining = r - 1; + } + + resubscribe(); + } + + void resubscribe() { + if (WIP.getAndIncrement(this) == 0) { + do { + if (isCancelled()) { + return; + } + + long c = produced; + if (c != 0L) { + produced = 0L; + produced(c); + } + + source.subscribe(this); + + } while (WIP.decrementAndGet(this) != 0); + } + } + + @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/FluxRetryWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxRetryWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxRetryWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.stream.Stream; + +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 reactor.util.context.ContextView; +import reactor.util.retry.Retry; + +/** + * Retries a source when a companion sequence signals + * an item in response to the main's error signal. + *

+ *

If the companion sequence signals when the main source is active, the repeat + * attempt is suppressed and any terminal signal will terminate the main source with the same signal immediately. + * + * @param the source value type + * @see Reactive-Streams-Commons + */ +final class FluxRetryWhen extends InternalFluxOperator { + + final Retry whenSourceFactory; + + FluxRetryWhen(Flux source, Retry whenSourceFactory) { + super(source); + this.whenSourceFactory = Objects.requireNonNull(whenSourceFactory, "whenSourceFactory"); + } + + static void subscribe(CoreSubscriber s, + Retry whenSourceFactory, + CorePublisher source) { + RetryWhenOtherSubscriber other = new RetryWhenOtherSubscriber(); + + CoreSubscriber serial = Operators.serialize(s); + + RetryWhenMainSubscriber main = + new RetryWhenMainSubscriber<>(serial, other.completionSignal, source, whenSourceFactory.retryContext()); + + other.main = main; + serial.onSubscribe(main); + + Publisher p; + try { + p = Objects.requireNonNull(whenSourceFactory.generateCompanion(other), "The whenSourceFactory returned a null Publisher"); + } + catch (Throwable e) { + s.onError(Operators.onOperatorError(e, s.currentContext())); + return; + } + p.subscribe(other); + + if (!main.cancelled) { + source.subscribe(main); + } + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + subscribe(actual, whenSourceFactory, source); + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class RetryWhenMainSubscriber extends Operators.MultiSubscriptionSubscriber + implements Retry.RetrySignal { + + final Operators.DeferredSubscription otherArbiter; + + final Sinks.Many signaller; + + final CorePublisher source; + + long totalFailureIndex = 0L; + long subsequentFailureIndex = 0L; + @Nullable + Throwable lastFailure = null; + final ContextView retryContext; + + Context context; + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(RetryWhenMainSubscriber.class, "wip"); + + long produced; + + RetryWhenMainSubscriber(CoreSubscriber actual, + Sinks.Many signaller, + CorePublisher source, + ContextView retryContext) { + super(actual); + this.signaller = signaller; + this.source = source; + this.otherArbiter = new Operators.DeferredSubscription(); + this.context = actual.currentContext(); + this.retryContext = retryContext; + } + + @Override + public long totalRetries() { + return this.totalFailureIndex - 1; + } + + @Override + public long totalRetriesInARow() { + return this.subsequentFailureIndex - 1; + } + + @Override + public Throwable failure() { + assert this.lastFailure != null; + return this.lastFailure; + } + + @Override + public ContextView retryContextView() { + return retryContext; + } + + @Override + public Context currentContext() { + return this.context; + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(signaller), otherArbiter); + } + + @Override + public void cancel() { + if (!cancelled) { + otherArbiter.cancel(); + super.cancel(); + } + } + + void swap(Subscription w) { + otherArbiter.set(w); + } + + @Override + public void onNext(T t) { + subsequentFailureIndex = 0; + actual.onNext(t); + + produced++; + } + + @Override + public void onError(Throwable t) { + totalFailureIndex++; + subsequentFailureIndex++; + lastFailure = t; + long p = produced; + if (p != 0L) { + produced = 0; + produced(p); + } + + signaller.emitNext(this, Sinks.EmitFailureHandler.FAIL_FAST); + // request after signalling, otherwise it may race + otherArbiter.request(1); + } + + @Override + public void onComplete() { + lastFailure = null; + otherArbiter.cancel(); + + actual.onComplete(); + } + + void resubscribe(Object trigger) { + if (WIP.getAndIncrement(this) == 0) { + do { + if (cancelled) { + return; + } + + //flow that emit a Context as a trigger for the re-subscription are + //used to REPLACE the currentContext() + if (trigger instanceof ContextView) { + this.context = this.context.putAll((ContextView) trigger); + } + + source.subscribe(this); + + } while (WIP.decrementAndGet(this) != 0); + } + } + + void whenError(Throwable e) { + super.cancel(); + + actual.onError(e); + } + + void whenComplete() { + super.cancel(); + + actual.onComplete(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + } + + static final class RetryWhenOtherSubscriber extends Flux + implements InnerConsumer, OptimizableOperator { + RetryWhenMainSubscriber main; + + final Sinks.Many completionSignal = Sinks.many().multicast().onBackpressureBuffer(); + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return main.otherArbiter; + if (key == Attr.ACTUAL) return main; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + main.swap(s); + } + + @Override + public void onNext(Object t) { + main.resubscribe(t); + } + + @Override + public void onError(Throwable t) { + main.whenError(t); + } + + @Override + public void onComplete() { + main.whenComplete(); + } + + @Override + public void subscribe(CoreSubscriber actual) { + completionSignal.asFlux().subscribe(actual); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public CorePublisher source() { + return completionSignal.asFlux(); + } + + @Override + public OptimizableOperator nextOptimizableSource() { + return null; + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSample.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSample.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSample.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2016-2021 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.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Samples the main source and emits its latest value whenever the other Publisher + * signals a value. + *

+ *

+ * Termination of either Publishers will result in termination for the Subscriber + * as well. + *

+ *

+ * Both Publishers will run in unbounded mode because the backpressure + * would interfere with the sampling precision. + * + * @param the input and output value type + * @param the value type of the sampler (irrelevant) + * @see Reactive-Streams-Commons + */ +final class FluxSample extends InternalFluxOperator { + + final Publisher other; + + FluxSample(Flux source, Publisher other) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CoreSubscriber serial = Operators.serialize(actual); + + SampleMainSubscriber main = new SampleMainSubscriber<>(serial); + + actual.onSubscribe(main); + + other.subscribe(new SampleOther<>(main)); + return main; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SampleMainSubscriber implements InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + + volatile T value; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater VALUE = + AtomicReferenceFieldUpdater.newUpdater(SampleMainSubscriber.class, Object.class, "value"); + + volatile Subscription main; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater MAIN = + AtomicReferenceFieldUpdater.newUpdater(SampleMainSubscriber.class, Subscription.class, "main"); + volatile Subscription other; + + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater OTHER = + AtomicReferenceFieldUpdater.newUpdater(SampleMainSubscriber.class, Subscription.class, "other"); + volatile long requested; + + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SampleMainSubscriber.class, "requested"); + + SampleMainSubscriber(CoreSubscriber actual) { + this.actual = actual; + this.ctx = actual.currentContext(); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(other)); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.PARENT) return main; + if (key == Attr.CANCELLED) return main == Operators.cancelledSubscription(); + if (key == Attr.BUFFERED) return value != null ? 1 : 0; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (!MAIN.compareAndSet(this, null, s)) { + s.cancel(); + if (main != Operators.cancelledSubscription()) { + Operators.reportSubscriptionSet(); + } + return; + } + s.request(Long.MAX_VALUE); + } + + void cancelMain() { + Subscription s = main; + if (s != Operators.cancelledSubscription()) { + s = MAIN.getAndSet(this, Operators.cancelledSubscription()); + if (s != null && s != Operators.cancelledSubscription()) { + s.cancel(); + } + } + } + + void cancelOther() { + Subscription s = other; + if (s != Operators.cancelledSubscription()) { + s = OTHER.getAndSet(this, Operators.cancelledSubscription()); + if (s != null && s != Operators.cancelledSubscription()) { + s.cancel(); + } + } + } + + void setOther(Subscription s) { + if (!OTHER.compareAndSet(this, null, s)) { + s.cancel(); + if (other != Operators.cancelledSubscription()) { + Operators.reportSubscriptionSet(); + } + return; + } + s.request(Long.MAX_VALUE); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + cancelMain(); + cancelOther(); + } + + @Override + public void onNext(T t) { + Object old = VALUE.getAndSet(this, t); + if (old != null) { + Operators.onDiscard(old, ctx); + } + } + + @Override + public void onError(Throwable t) { + cancelOther(); + + actual.onError(t); + } + + @Override + public void onComplete() { + cancelOther(); + //implementation note: if the sampler triggers at the exact same time as the + // last source value being emitted, it could be that the sampler "wins" and + // suppresses the value by cancelling the main. In any other case, the block + // below ensures last source value is always part of the sample. + T v = value; + if (v != null) { + actual.onNext(value); + } + + actual.onComplete(); + } + + @SuppressWarnings("unchecked") + @Nullable + T getAndNullValue() { + return (T) VALUE.getAndSet(this, null); + } + + void decrement() { + REQUESTED.decrementAndGet(this); + } + } + + static final class SampleOther implements InnerConsumer { + final SampleMainSubscriber main; + + SampleOther(SampleMainSubscriber main) { + this.main = main; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return main.other; + if (key == Attr.ACTUAL) return main; + if (key == Attr.CANCELLED) return main.other == Operators.cancelledSubscription(); + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + main.setOther(s); + } + + @Override + public void onNext(U t) { + SampleMainSubscriber m = main; + + T v = m.getAndNullValue(); + + if (v != null) { + if (m.requested != 0L) { + m.actual.onNext(v); + + if (m.requested != Long.MAX_VALUE) { + m.decrement(); + } + return; + } + + m.cancel(); + m.actual.onError(Exceptions.failWithOverflow("Can't signal value due to lack of requests")); + Operators.onDiscard(v, m.ctx); + } + } + + @Override + public void onError(Throwable t) { + SampleMainSubscriber m = main; + + m.cancelMain(); + + m.actual.onError(t); + } + + @Override + public void onComplete() { + SampleMainSubscriber m = main; + + m.cancelMain(); + + m.actual.onComplete(); + } + + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSampleFirst.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSampleFirst.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSampleFirst.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +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.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Takes a value from upstream then uses the duration provided by a + * generated Publisher to skip other values until that other Publisher signals. + * + * @param the source and output value type + * @param the value type of the publisher signalling the end of the throttling + * duration + * + * @see Reactive-Streams-Commons + */ +final class FluxSampleFirst extends InternalFluxOperator { + + final Function> throttler; + + FluxSampleFirst(Flux source, + Function> throttler) { + super(source); + this.throttler = Objects.requireNonNull(throttler, "throttler"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + SampleFirstMain main = new SampleFirstMain<>(actual, throttler); + + actual.onSubscribe(main); + + return main; + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SampleFirstMain implements InnerOperator { + + final Function> throttler; + final CoreSubscriber actual; + final Context ctx; + + volatile boolean gate; + + volatile Subscription s; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(SampleFirstMain.class, + Subscription.class, + "s"); + + volatile Subscription other; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater OTHER = + AtomicReferenceFieldUpdater.newUpdater(SampleFirstMain.class, + Subscription.class, + "other"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SampleFirstMain.class, "requested"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(SampleFirstMain.class, "wip"); + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(SampleFirstMain.class, + Throwable.class, + "error"); + + SampleFirstMain(CoreSubscriber actual, + Function> throttler) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.throttler = throttler; + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(other)); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return s; + if (key == Attr.ERROR) return error; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + Operators.terminate(S, this); + Operators.terminate(OTHER, this); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (!gate) { + gate = true; + + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + actual.onNext(t); + if (WIP.decrementAndGet(this) != 0) { + handleTermination(); + return; + } + } + else { + return; + } + + Publisher p; + + try { + p = Objects.requireNonNull(throttler.apply(t), + "The throttler returned a null publisher"); + } + catch (Throwable e) { + Operators.terminate(S, this); + error(Operators.onOperatorError(null, e, t, ctx)); + return; + } + + SampleFirstOther other = new SampleFirstOther<>(this); + + if (Operators.replace(OTHER, this, other)) { + p.subscribe(other); + } + } + else { + Operators.onDiscard(t, ctx); + } + } + + void handleTermination() { + Throwable e = Exceptions.terminate(ERROR, this); + if (e != null && e != Exceptions.TERMINATED) { + actual.onError(e); + } + else { + actual.onComplete(); + } + } + + void error(Throwable e) { + if (Exceptions.addThrowable(ERROR, this, e)) { + if (WIP.getAndIncrement(this) == 0) { + handleTermination(); + } + } + else { + Operators.onErrorDropped(e, ctx); + } + } + + @Override + public void onError(Throwable t) { + Operators.terminate(OTHER, this); + + error(t); + } + + @Override + public void onComplete() { + Operators.terminate(OTHER, this); + + if (WIP.getAndIncrement(this) == 0) { + handleTermination(); + } + } + + void otherNext() { + gate = false; + } + + void otherError(Throwable e) { + Operators.terminate(S, this); + + error(e); + } + } + + static final class SampleFirstOther extends Operators.DeferredSubscription + implements InnerConsumer { + + final SampleFirstMain main; + + SampleFirstOther(SampleFirstMain main) { + this.main = main; + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) return main; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (set(s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(U t) { + cancel(); + + main.otherNext(); + } + + @Override + public void onError(Throwable t) { + main.otherError(t); + } + + @Override + public void onComplete() { + main.otherNext(); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSampleTimeout.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSampleTimeout.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSampleTimeout.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +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; + +/** + * Emits the last value from upstream only if there were no newer values emitted + * during the time window provided by a publisher for that particular last value. + * + * @param the source value type + * @param the value type of the duration publisher + * + * @see Reactive-Streams-Commons + */ +final class FluxSampleTimeout extends InternalFluxOperator { + + final Function> throttler; + + final Supplier> queueSupplier; + + FluxSampleTimeout(Flux source, + Function> throttler, + Supplier> queueSupplier) { + super(source); + this.throttler = Objects.requireNonNull(throttler, "throttler"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + Queue> q = (Queue) queueSupplier.get(); + + SampleTimeoutMain main = new SampleTimeoutMain<>(actual, throttler, q); + + actual.onSubscribe(main); + + return main; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SampleTimeoutMainimplements InnerOperator { + + final Function> throttler; + final Queue> queue; + final CoreSubscriber actual; + final Context ctx; + + volatile Subscription s; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(SampleTimeoutMain.class, + Subscription.class, + "s"); + + volatile Subscription other; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + OTHER = AtomicReferenceFieldUpdater.newUpdater(SampleTimeoutMain.class, + Subscription.class, + "other"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SampleTimeoutMain.class, "requested"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(SampleTimeoutMain.class, "wip"); + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(SampleTimeoutMain.class, + Throwable.class, + "error"); + + volatile boolean done; + + volatile boolean cancelled; + + volatile long index; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater INDEX = + AtomicLongFieldUpdater.newUpdater(SampleTimeoutMain.class, "index"); + + SampleTimeoutMain(CoreSubscriber actual, + Function> throttler, + Queue> queue) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.throttler = throttler; + this.queue = queue; + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(other)); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.PARENT) return s; + if (key == Attr.ERROR) return error; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.BUFFERED) return queue.size(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + Operators.terminate(S, this); + Operators.terminate(OTHER, this); + Operators.onDiscardQueueWithClear(queue, ctx, SampleTimeoutOther::toStream); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + long idx = INDEX.incrementAndGet(this); + + if (!Operators.set(OTHER, this, Operators.emptySubscription())) { + return; + } + + Publisher p; + + try { + p = Objects.requireNonNull(throttler.apply(t), + "throttler returned a null publisher"); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + return; + } + + SampleTimeoutOther os = new SampleTimeoutOther<>(this, t, idx); + + if (Operators.replace(OTHER, this, os)) { + p.subscribe(os); + } + } + + void error(Throwable t) { + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + drain(); + } + else { + Operators.onErrorDropped(t, ctx); + } + } + + @Override + public void onError(Throwable t) { + Operators.terminate(OTHER, this); + + error(t); + } + + @Override + public void onComplete() { + Subscription o = other; + if (o instanceof FluxSampleTimeout.SampleTimeoutOther) { + SampleTimeoutOther os = (SampleTimeoutOther) o; + os.cancel(); + os.onComplete(); + } + done = true; + drain(); + } + + void otherNext(SampleTimeoutOther other) { + queue.offer(other); + drain(); + } + + void otherError(long idx, Throwable e) { + if (idx == index) { + Operators.terminate(S, this); + + error(e); + } + else { + Operators.onErrorDropped(e, ctx); + } + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + final Subscriber a = actual; + final Queue> q = queue; + + int missed = 1; + + for (; ; ) { + + for (; ; ) { + boolean d = done; + + SampleTimeoutOther o = q.poll(); + + boolean empty = o == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + if (o.index == index) { + long r = requested; + if (r != 0) { + a.onNext(o.value); + if (r != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + } + else { + cancel(); + + Operators.onDiscardQueueWithClear(q, ctx, SampleTimeoutOther::toStream); + + Throwable e = Exceptions.failWithOverflow( + "Could not emit value due to lack of requests"); + Exceptions.addThrowable(ERROR, this, e); + e = Exceptions.terminate(ERROR, this); + + a.onError(e); + return; + } + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue> q) { + if (cancelled) { + Operators.onDiscardQueueWithClear(q, ctx, SampleTimeoutOther::toStream); + return true; + } + if (d) { + Throwable e = Exceptions.terminate(ERROR, this); + if (e != null && e != Exceptions.TERMINATED) { + cancel(); + + Operators.onDiscardQueueWithClear(q, ctx, SampleTimeoutOther::toStream); + + a.onError(e); + return true; + } + else if (empty) { + + a.onComplete(); + return true; + } + } + return false; + } + } + + static final class SampleTimeoutOther extends Operators.DeferredSubscription + implements InnerConsumer { + + final SampleTimeoutMain main; + + final T value; + + final long index; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(SampleTimeoutOther.class, "once"); + + SampleTimeoutOther(SampleTimeoutMain main, T value, long index) { + this.main = main; + this.value = value; + this.index = index; + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return once == 1; + if (key == Attr.ACTUAL) return main; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (set(s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(U t) { + if (ONCE.compareAndSet(this, 0, 1)) { + cancel(); + + main.otherNext(this); + } + } + + @Override + public void onError(Throwable t) { + if (ONCE.compareAndSet(this, 0, 1)) { + main.otherError(index, t); + } + else { + Operators.onErrorDropped(t, main.currentContext()); + } + } + + @Override + public void onComplete() { + if (ONCE.compareAndSet(this, 0, 1)) { + main.otherNext(this); + } + } + + final Stream toStream() { + return Stream.of(value); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxScan.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxScan.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxScan.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2016-2021 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.BiFunction; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Accumulates the source values with an accumulator function and + * returns the intermediate results of this function. + *

+ * Unlike {@link FluxScan}, this operator doesn't take an initial value + * but treats the first source value as initial value. + *
+ * The accumulation works as follows: + *


+ * result[0] = accumulator(source[0], source[1])
+ * result[1] = accumulator(result[0], source[2])
+ * result[2] = accumulator(result[1], source[3])
+ * ...
+ * 
+ * + * @param the input and accumulated value type + * @see Reactive-Streams-Commons + */ +final class FluxScan extends InternalFluxOperator { + + final BiFunction accumulator; + + FluxScan(Flux source, BiFunction accumulator) { + super(source); + this.accumulator = Objects.requireNonNull(accumulator, "accumulator"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new ScanSubscriber<>(actual, accumulator); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ScanSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + + final BiFunction accumulator; + + Subscription s; + + T value; + + boolean done; + + ScanSubscriber(CoreSubscriber actual, BiFunction accumulator) { + this.actual = actual; + this.accumulator = accumulator; + } + + @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; + } + + T v = value; + + if (v != null) { + try { + t = Objects.requireNonNull(accumulator.apply(v, t), + "The accumulator returned a null value"); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + } + value = t; + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + value = null; + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + value = null; + actual.onComplete(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.BUFFERED) return value != null ? 1 : 0; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + //unless we want to pay a volatile, cancel() nulling out might race with onNext + //and result in re-setting value, retaining it. So we don't null out, nor discard. + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxScanSeed.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxScanSeed.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxScanSeed.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +import static reactor.core.Scannable.Attr.RUN_STYLE; +import static reactor.core.Scannable.Attr.RunStyle.SYNC; + +/** + * Aggregates the source values with the help of an accumulator function + * and emits the intermediate results. + *

+ * The accumulation works as follows: + *


+ * result[0] = initialValue;
+ * result[1] = accumulator(result[0], source[0])
+ * result[2] = accumulator(result[1], source[1])
+ * result[3] = accumulator(result[2], source[2])
+ * ...
+ * 
+ * + * @param the source value type + * @param the aggregate type + * + * @see Reactive-Streams-Commons + */ +final class FluxScanSeed extends InternalFluxOperator { + + final BiFunction accumulator; + + final Supplier initialSupplier; + + FluxScanSeed(Flux source, + Supplier initialSupplier, + BiFunction accumulator) { + super(source); + this.accumulator = Objects.requireNonNull(accumulator, "accumulator"); + this.initialSupplier = Objects.requireNonNull(initialSupplier, "initialSupplier"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + ScanSeedCoordinator coordinator = + new ScanSeedCoordinator<>(actual, source, accumulator, initialSupplier); + + actual.onSubscribe(coordinator); + + if (!coordinator.isCancelled()) { + coordinator.onComplete(); + } + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == RUN_STYLE) return SYNC; + return super.scanUnsafe(key); + } + + static final class ScanSeedCoordinator + extends Operators.MultiSubscriptionSubscriber { + + final Supplier initialSupplier; + final Flux source; + final BiFunction accumulator; + volatile int wip; + long produced; + private ScanSeedSubscriber seedSubscriber; + + ScanSeedCoordinator(CoreSubscriber actual, Flux source, + BiFunction accumulator, + Supplier initialSupplier) { + super(actual); + this.source = source; + this.accumulator = accumulator; + this.initialSupplier = initialSupplier; + } + + @Override + public void onComplete() { + if (WIP.getAndIncrement(this) == 0) { + do { + if (isCancelled()) { + return; + } + + if (null != seedSubscriber && subscription == seedSubscriber) { + actual.onComplete(); + return; + } + + long c = produced; + if (c != 0L) { + produced = 0L; + produced(c); + } + + if (null == seedSubscriber) { + R initialValue; + + try { + initialValue = Objects.requireNonNull(initialSupplier.get(), + "The initial value supplied is null"); + } + catch (Throwable e) { + onError(Operators.onOperatorError(e, actual.currentContext())); + return; + } + + onSubscribe(Operators.scalarSubscription(this, initialValue)); + seedSubscriber = + new ScanSeedSubscriber<>(this, accumulator, initialValue); + } + else { + source.subscribe(seedSubscriber); + } + + if (isCancelled()) { + return; + } + } + while (WIP.decrementAndGet(this) != 0); + } + + } + + @Override + public void onNext(R r) { + produced++; + actual.onNext(r); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == RUN_STYLE) return SYNC; + return super.scanUnsafe(key); + } + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ScanSeedCoordinator.class, "wip"); + } + + static final class ScanSeedSubscriber implements InnerOperator { + + final CoreSubscriber actual; + + final BiFunction accumulator; + + Subscription s; + + R value; + + boolean done; + + ScanSeedSubscriber(CoreSubscriber actual, + BiFunction accumulator, + R initialValue) { + this.actual = actual; + this.accumulator = accumulator; + this.value = initialValue; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + value = null; + actual.onComplete(); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + value = null; + actual.onError(t); + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + R r = value; + + try { + r = Objects.requireNonNull(accumulator.apply(r, t), + "The accumulator returned a null value"); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + + actual.onNext(r); + value = r; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.TERMINATED) { + return done; + } + if (key == RUN_STYLE) { + return SYNC; + } + return InnerOperator.super.scanUnsafe(key); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSink.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSink.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSink.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2016-2021 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.Function; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.util.context.Context; + +/** + * Wrapper API around a downstream Subscriber for emitting any number of + * next signals followed by zero or one onError/onComplete. + *

+ * @param the value type + */ +public interface FluxSink { + + /** + * Emit a non-null element, generating an {@link Subscriber#onNext(Object) onNext} signal. + *

+ * Might throw an unchecked exception in case of a fatal error downstream which cannot + * be propagated to any asynchronous handler (aka a bubbling exception). + * + * @param t the value to emit, not null + * @return this sink for chaining further signals + **/ + FluxSink next(T t); + + /** + * Terminate the sequence successfully, generating an {@link Subscriber#onComplete() onComplete} + * signal. + * + * @see Subscriber#onComplete() + */ + void complete(); + + /** + * Fail the sequence, generating an {@link Subscriber#onError(Throwable) onError} + * signal. + * + * @param e the exception to signal, not null + * @see Subscriber#onError(Throwable) + */ + void error(Throwable e); + + /** + * Return the current subscriber {@link Context}. + *

+ * {@link Context} can be enriched via {@link Flux#contextWrite(Function)} + * operator or directly by a child subscriber overriding + * {@link CoreSubscriber#currentContext()} + * + * @return the current subscriber {@link Context}. + */ + Context currentContext(); + + + /** + * The current outstanding request amount. + * @return the current outstanding request amount + */ + long requestedFromDownstream(); + + /** + * Returns true if the downstream cancelled the sequence. + * @return true if the downstream cancelled the sequence + */ + boolean isCancelled(); + + /** + * Attaches a {@link LongConsumer} to this {@link FluxSink} that will be notified of + * 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)}, + * the consumer + * is invoked for every request to enable a hybrid backpressure-enabled push/pull model. + * 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)}, + * the consumer is invoked with an initial request of {@code Long.MAX_VALUE} when this method + * is invoked. + * + * @param consumer the consumer to invoke on each request + * @return {@link FluxSink} with a consumer that is notified of requests + */ + FluxSink onRequest(LongConsumer consumer); + + /** + * Attach a {@link Disposable} as a callback for when this {@link FluxSink} is + * cancelled. At most one callback can be registered, and subsequent calls to this method + * will result in the immediate disposal of the extraneous {@link Disposable}. + *

+ * The callback is only relevant when the downstream {@link Subscription} is {@link Subscription#cancel() cancelled}. + * + * @param d the {@link Disposable} to use as a callback + * @return the {@link FluxSink} with a cancellation callback + * @see #onCancel(Disposable) onDispose(Disposable) for a callback that covers cancellation AND terminal signals + */ + FluxSink onCancel(Disposable d); + + /** + * Attach a {@link Disposable} as a callback for when this {@link FluxSink} is effectively + * disposed, that is it cannot be used anymore. This includes both having played terminal + * signals (onComplete, onError) and having been cancelled (see {@link #onCancel(Disposable)}). + * At most one callback can be registered, and subsequent calls to this method will result in + * the immediate disposal of the extraneous {@link Disposable}. + *

+ * Note that the "dispose" term is used from the perspective of the sink. Not to + * be confused with {@link Flux#subscribe()}'s {@link Disposable#dispose()} method, which + * maps to disposing the {@link Subscription} (effectively, a {@link Subscription#cancel()} + * signal). + * + * @param d the {@link Disposable} to use as a callback + * @return the {@link FluxSink} with a callback invoked on any terminal signal or on cancellation + * @see #onCancel(Disposable) onCancel(Disposable) for a cancellation-only callback + */ + FluxSink onDispose(Disposable d); + + /** + * Enumeration for backpressure handling. + */ + enum OverflowStrategy { + /** + * Completely ignore downstream backpressure requests. + *

+ * This may yield {@link IllegalStateException} when queues get full downstream. + */ + IGNORE, + /** + * Signal an {@link IllegalStateException} when the downstream can't keep up + */ + ERROR, + /** + * Drop the incoming signal if the downstream is not ready to receive it. + */ + DROP, + /** + * Downstream will get only the latest signals from upstream. + */ + LATEST, + /** + * Buffer all signals if the downstream can't keep up. + *

+ * Warning! This does unbounded buffering and may lead to {@link OutOfMemoryError}. + */ + BUFFER + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSkip.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSkip.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSkip.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2016-2021 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.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Skips the first N elements from a reactive stream. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxSkip extends InternalFluxOperator { + + final long n; + + FluxSkip(Flux source, long n) { + super(source); + if (n < 0) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + this.n = n; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new SkipSubscriber<>(actual, n); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + //Fixme Does not implement ConditionalSubscriber until the full chain of operators + // supports fully conditional, requesting N onSubscribe cost is offset + + static final class SkipSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + + long remaining; + + Subscription s; + + SkipSubscriber(CoreSubscriber actual, long n) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.remaining = n; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + long n = remaining; + actual.onSubscribe(this); + s.request(n); + } + } + + @Override + public void onNext(T t) { + long r = remaining; + if (r == 0L) { + actual.onNext(t); + } + else { + Operators.onDiscard(t, ctx); + remaining = r - 1; + } + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @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 CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipLast.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipLast.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipLast.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2016-2021 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.ArrayDeque; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Skips the last N elements from the source stream. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxSkipLast extends InternalFluxOperator { + + final int n; + + FluxSkipLast(Flux source, int n) { + super(source); + if (n < 0) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + this.n = n; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new SkipLastSubscriber<>(actual, n); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + //Fixme Does not implement ConditionalSubscriber until the full chain of operators + // supports fully conditional, requesting N onSubscribe cost is offset + + static final class SkipLastSubscriber + extends ArrayDeque + implements InnerOperator { + final CoreSubscriber actual; + + final int n; + + Subscription s; + + SkipLastSubscriber(CoreSubscriber actual, int n) { + this.actual = actual; + this.n = n; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + + s.request(n); + } + } + + @Override + public void onNext(T t) { + if (size() == n) { + actual.onNext(pollFirst()); + } + offerLast(t); + + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + Operators.onDiscardQueueWithClear(this, actual.currentContext(), null); + } + + @Override + public void onComplete() { + actual.onComplete(); + Operators.onDiscardQueueWithClear(this, actual.currentContext(), null); + } + + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.PREFETCH) return n; + if (key == Attr.BUFFERED) return size(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + Operators.onDiscardQueueWithClear(this, actual.currentContext(), null); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipUntil.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipUntil.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipUntil.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Skips source values until a predicate returns + * true for the value. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxSkipUntil extends InternalFluxOperator { + + final Predicate predicate; + + FluxSkipUntil(Flux source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new SkipUntilSubscriber<>(actual, predicate); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SkipUntilSubscriber + implements ConditionalSubscriber, InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + + final Predicate predicate; + + Subscription s; + + boolean done; + + boolean doneSkipping; + + SkipUntilSubscriber(CoreSubscriber actual, Predicate predicate) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.predicate = predicate; + } + + @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, ctx); + return; + } + + if (doneSkipping){ + actual.onNext(t); + return; + } + boolean b; + + try { + b = predicate.test(t); + } catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + return; + } + + if (b) { + doneSkipping = true; + actual.onNext(t); + return; + } + + Operators.onDiscard(t, ctx); + s.request(1); + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, ctx); + return true; + } + + if (doneSkipping) { + actual.onNext(t); + return true; + } + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + + return true; + } + + if (b) { + doneSkipping = true; + actual.onNext(t); + return true; + } + + Operators.onDiscard(t, ctx); + return false; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, ctx); + return; + } + done = true; + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + actual.onComplete(); + } + + @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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipUntilOther.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipUntilOther.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipUntilOther.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2016-2021 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.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Skips values from the main publisher until the other publisher signals + * an onNext or onComplete. + * + * @param the value type of the main Publisher + * @param the value type of the other Publisher + * + * @see https://github.com/reactor/reactive-streams-commons + */ +final class FluxSkipUntilOther extends InternalFluxOperator { + + final Publisher other; + + FluxSkipUntilOther(Flux source, Publisher other) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + SkipUntilMainSubscriber mainSubscriber = new SkipUntilMainSubscriber<>(actual); + + SkipUntilOtherSubscriber otherSubscriber = new SkipUntilOtherSubscriber<>(mainSubscriber); + + other.subscribe(otherSubscriber); + + return mainSubscriber; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SkipUntilOtherSubscriber implements InnerConsumer { + + final SkipUntilMainSubscriber main; + + SkipUntilOtherSubscriber(SkipUntilMainSubscriber main) { + this.main = main; + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return main.other == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return main.other; + if (key == Attr.ACTUAL) return main; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + main.setOther(s); + + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(U t) { + if (main.gate) { + return; + } + SkipUntilMainSubscriber m = main; + m.other.cancel(); + m.gate = true; + m.other = Operators.cancelledSubscription(); + } + + @Override + public void onError(Throwable t) { + SkipUntilMainSubscriber m = main; + if (m.gate) { + Operators.onErrorDropped(t, main.currentContext()); + return; + } + m.onError(t); + } + + @Override + public void onComplete() { + SkipUntilMainSubscriber m = main; + if (m.gate) { + return; + } + m.gate = true; + m.other = Operators.cancelledSubscription(); + } + + + } + + static final class SkipUntilMainSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + final Context ctx; + + volatile Subscription main; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + MAIN = + AtomicReferenceFieldUpdater.newUpdater(SkipUntilMainSubscriber.class, + Subscription.class, + "main"); + + volatile Subscription other; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + OTHER = + AtomicReferenceFieldUpdater.newUpdater(SkipUntilMainSubscriber.class, + Subscription.class, + "other"); + + volatile boolean gate; + + SkipUntilMainSubscriber(CoreSubscriber actual) { + this.actual = Operators.serialize(actual); + this.ctx = actual.currentContext(); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return main; + if (key == Attr.CANCELLED) return main == Operators.cancelledSubscription(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(other)); + } + + void setOther(Subscription s) { + if (!OTHER.compareAndSet(this, null, s)) { + s.cancel(); + if (other != Operators.cancelledSubscription()) { + Operators.reportSubscriptionSet(); + } + } + } + + @Override + public void request(long n) { + main.request(n); + } + + @Override + public void cancel() { + Operators.terminate(MAIN, this); + Operators.terminate(OTHER, this); + } + + @Override + public void onSubscribe(Subscription s) { + if (!MAIN.compareAndSet(this, null, s)) { + s.cancel(); + if (main != Operators.cancelledSubscription()) { + Operators.reportSubscriptionSet(); + } + } + else { + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (gate) { + actual.onNext(t); + } + else { + Operators.onDiscard(t, ctx); + main.request(1); + } + } + + @Override + public void onError(Throwable t) { + if (MAIN.compareAndSet(this, null, Operators.cancelledSubscription())) { + Operators.error(actual, t); + return; + } + else if (main == Operators.cancelledSubscription()){ + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + cancel(); + + actual.onError(t); + } + + @Override + public void onComplete() { + Operators.terminate(OTHER, this); + + actual.onComplete(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipWhile.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipWhile.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSkipWhile.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Skips source values while a predicate returns + * true for the value. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxSkipWhile extends InternalFluxOperator { + + final Predicate predicate; + + FluxSkipWhile(Flux source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new SkipWhileSubscriber<>(actual, predicate); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SkipWhileSubscriber + implements ConditionalSubscriber, InnerOperator { + final CoreSubscriber actual; + final Context ctx; + + final Predicate predicate; + + Subscription s; + + boolean done; + + boolean skipped; + + SkipWhileSubscriber(CoreSubscriber actual, Predicate predicate) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.predicate = predicate; + } + + @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, ctx); + return; + } + + if (skipped){ + actual.onNext(t); + return; + } + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + return; + } + + if (b) { + Operators.onDiscard(t, ctx); + s.request(1); + return; + } + + skipped = true; + actual.onNext(t); + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, ctx); + return true; + } + + if (skipped) { + actual.onNext(t); + return true; + } + boolean b; + + try { + b = predicate.test(t); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + + return true; + } + + if (b) { + Operators.onDiscard(t, ctx); + return false; + } + + skipped = true; + actual.onNext(t); + return true; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, ctx); + return; + } + done = true; + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + actual.onComplete(); + } + + @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); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSource.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSource.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSource.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * A connecting {@link Flux} Publisher (right-to-left from a composition chain perspective) + * + * @param Upstream type + */ +final class FluxSource extends Flux implements SourceProducer, + OptimizableOperator { + + + final Publisher source; + + @Nullable + final OptimizableOperator optimizableOperator; + + /** + * Build a {@link FluxSource} wrapper around the passed parent {@link Publisher} + * + * @param source the {@link Publisher} to decorate + */ + FluxSource(Publisher source) { + this.source = Objects.requireNonNull(source); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + /** + * Default is simply delegating and decorating with {@link Flux} API. Note this + * assumes an identity between input and output types. + * @param actual + */ + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + source.subscribe(actual); + } + + @Override + public final CorePublisher source() { + return this; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + @Nullable + 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; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * @author Stephane Maldini + */ +final class FluxSourceFuseable extends Flux implements Fuseable, SourceProducer, + OptimizableOperator { + + final Publisher source; + + @Nullable + final OptimizableOperator optimizableOperator; + + FluxSourceFuseable(Publisher source) { + this.source = Objects.requireNonNull(source); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + /** + * Default is simply delegating and decorating with {@link Flux} API. Note this + * assumes an identity between input and output types. + * @param actual + */ + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + source.subscribe(actual); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public final CorePublisher source() { + return this; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + @Nullable + public Object scanUnsafe(Scannable.Attr key) { + if (key == Scannable.Attr.PREFETCH) return getPrefetch(); + if (key == Scannable.Attr.PARENT) return source; + if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceMono.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceMono.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceMono.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016-2021 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.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; + +/** + * A connecting {@link Flux} Publisher (right-to-left from a composition chain perspective) + * + * @param Upstream type + */ +final class FluxSourceMono extends FluxFromMonoOperator { + + + /** + * Build a {@link FluxSourceMono} wrapper around the passed parent {@link Publisher} + * + * @param source the {@link Publisher} to decorate + */ + FluxSourceMono(Mono source) { + super(source); + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return "FluxFromMono(" + Scannable.from(source).stepName() + ")"; + } + return "FluxFromMono(" + source.toString() + ")"; + } + + /** + * Default is simply delegating and decorating with {@link Flux} API. Note this + * assumes an identity between input and output types. + * @param actual + */ + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceMonoFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceMonoFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSourceMonoFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016-2021 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.Scannable; + +/** + * @author Stephane Maldini + */ +final class FluxSourceMonoFuseable extends FluxFromMonoOperator implements Fuseable { + + FluxSourceMonoFuseable(Mono source) { + super(source); + } + + /** + * Default is simply delegating and decorating with {@link Flux} API. Note this + * assumes an identity between input and output types. + * @param actual + */ + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + + @Override + public String stepName() { + if (source instanceof Scannable) { + return "FluxFromMono(" + Scannable.from(source).stepName() + ")"; + } + return "FluxFromMono(" + source.toString() + ")"; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); + return super.scanUnsafe(key); + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxStream.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxStream.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxStream.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.Objects; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Emits the contents of a Stream source. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxStream extends Flux implements Fuseable, SourceProducer { + + final Supplier> streamSupplier; + + FluxStream(Supplier> streamSupplier) { + this.streamSupplier = Objects.requireNonNull(streamSupplier, "streamSupplier"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Stream stream; + try { + stream = Objects.requireNonNull(streamSupplier.get(), + "The stream supplier returned a null Stream"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + + Iterator it; + 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 + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + + //although not required by AutoCloseable, Stream::close SHOULD be idempotent + //(at least the default AbstractPipeline implementation is) + FluxIterable.subscribe(actual, it, knownToBeFinite, stream::close); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOn.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOn.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOn.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Scheduler.Worker; +import reactor.util.annotation.Nullable; + +/** + * Subscribes to the source Publisher asynchronously through a scheduler function or + * ExecutorService. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxSubscribeOn extends InternalFluxOperator { + + final Scheduler scheduler; + final boolean requestOnSeparateThread; + + FluxSubscribeOn( + Flux source, + Scheduler scheduler, + boolean requestOnSeparateThread) { + super(source); + this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); + this.requestOnSeparateThread = requestOnSeparateThread; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + Worker worker = Objects.requireNonNull(scheduler.createWorker(), + "The scheduler returned a null Function"); + + SubscribeOnSubscriber parent = new SubscribeOnSubscriber<>(source, + actual, worker, requestOnSeparateThread); + actual.onSubscribe(parent); + + try { + worker.schedule(parent); + } + catch (RejectedExecutionException ree) { + if (parent.s != Operators.cancelledSubscription()) { + actual.onError(Operators.onRejectedExecution(ree, parent, null, null, + actual.currentContext())); + } + } + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + return super.scanUnsafe(key); + } + + static final class SubscribeOnSubscriber implements InnerOperator, Runnable { + + final CoreSubscriber actual; + + final CorePublisher source; + + final Worker worker; + final boolean requestOnSeparateThread; + + volatile Subscription s; + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(SubscribeOnSubscriber.class, + Subscription.class, + "s"); + + + volatile long requested; + + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SubscribeOnSubscriber.class, + "requested"); + + volatile Thread thread; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater THREAD = + AtomicReferenceFieldUpdater.newUpdater(SubscribeOnSubscriber.class, + Thread.class, + "thread"); + + SubscribeOnSubscriber(CorePublisher source, CoreSubscriber actual, + Worker worker, boolean requestOnSeparateThread) { + this.actual = actual; + this.worker = worker; + this.source = source; + this.requestOnSeparateThread = requestOnSeparateThread; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + long r = REQUESTED.getAndSet(this, 0L); + if (r != 0L) { + requestUpstream(r, s); + } + } + } + + void requestUpstream(final long n, final Subscription s) { + if (!requestOnSeparateThread || Thread.currentThread() == THREAD.get(this)) { + s.request(n); + } + else { + try { + worker.schedule(() -> s.request(n)); + } + catch (RejectedExecutionException ree) { + if(!worker.isDisposed()) { + //FIXME should not throw but if we implement strict + // serialization like in StrictSubscriber, onNext will carry an + // extra cost + throw Operators.onRejectedExecution(ree, this, null, null, + actual.currentContext()); + } + } + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + try { + actual.onError(t); + } + finally { + worker.dispose(); + } + } + + @Override + public void onComplete() { + actual.onComplete(); + worker.dispose(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Subscription s = S.get(this); + if (s != null) { + requestUpstream(n, s); + } + else { + Operators.addCap(REQUESTED, this, n); + s = S.get(this); + if (s != null) { + long r = REQUESTED.getAndSet(this, 0L); + if (r != 0L) { + requestUpstream(r, s); + } + } + + } + } + } + + @Override + public void run() { + THREAD.lazySet(this, Thread.currentThread()); + source.subscribe(this); + } + + @Override + public void cancel() { + Subscription a = s; + if (a != Operators.cancelledSubscription()) { + a = S.getAndSet(this, Operators.cancelledSubscription()); + if (a != null && a != Operators.cancelledSubscription()) { + a.cancel(); + } + } + worker.dispose(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.RUN_ON) return worker; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOnCallable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOnCallable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOnCallable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +/** + * Executes a Callable and emits its value on the given Scheduler. + * + * @param the value type + * @see https://github.com/reactor/reactive-streams-commons + */ +final class FluxSubscribeOnCallable extends Flux implements Fuseable, Scannable { + + final Callable callable; + + final Scheduler scheduler; + + FluxSubscribeOnCallable(Callable callable, Scheduler scheduler) { + this.callable = Objects.requireNonNull(callable, "callable"); + this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + CallableSubscribeOnSubscription parent = + new CallableSubscribeOnSubscription<>(actual, callable, scheduler); + actual.onSubscribe(parent); + + try { + Disposable f = scheduler.schedule(parent); + parent.setMainFuture(f); + } + catch (RejectedExecutionException ree) { + if(parent.state != CallableSubscribeOnSubscription.HAS_CANCELLED) { + actual.onError(Operators.onRejectedExecution(ree, actual.currentContext())); + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return null; + } + + static final class CallableSubscribeOnSubscription + implements QueueSubscription, InnerProducer, Runnable { + + final CoreSubscriber actual; + + final Callable callable; + + final Scheduler scheduler; + + volatile int state; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(CallableSubscribeOnSubscription.class, + "state"); + + T value; + static final int NO_REQUEST_HAS_VALUE = 1; + static final int HAS_REQUEST_NO_VALUE = 2; + static final int HAS_REQUEST_HAS_VALUE = 3; + static final int HAS_CANCELLED = 4; + + int fusionState; + + static final int NO_VALUE = 1; + static final int HAS_VALUE = 2; + static final int COMPLETE = 3; + + volatile Disposable mainFuture; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + MAIN_FUTURE = AtomicReferenceFieldUpdater.newUpdater( + CallableSubscribeOnSubscription.class, + Disposable.class, + "mainFuture"); + + volatile Disposable requestFuture; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + REQUEST_FUTURE = AtomicReferenceFieldUpdater.newUpdater( + CallableSubscribeOnSubscription.class, + Disposable.class, + "requestFuture"); + + CallableSubscribeOnSubscription(CoreSubscriber actual, + Callable callable, + Scheduler scheduler) { + this.actual = actual; + this.callable = callable; + this.scheduler = scheduler; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return state == HAS_CANCELLED; + if (key == Attr.BUFFERED) return value != null ? 1 : 0; + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public void cancel() { + state = HAS_CANCELLED; + fusionState = COMPLETE; + Disposable a = mainFuture; + if (a != OperatorDisposables.DISPOSED) { + a = MAIN_FUTURE.getAndSet(this, OperatorDisposables.DISPOSED); + if (a != null && a != OperatorDisposables.DISPOSED) { + a.dispose(); + } + } + a = requestFuture; + if (a != OperatorDisposables.DISPOSED) { + a = REQUEST_FUTURE.getAndSet(this, OperatorDisposables.DISPOSED); + if (a != null && a != OperatorDisposables.DISPOSED) { + a.dispose(); + } + } + } + + @Override + public void clear() { + value = null; + fusionState = COMPLETE; + } + + @Override + public boolean isEmpty() { + return fusionState == COMPLETE; + } + + @Override + @Nullable + public T poll() { + if (fusionState == HAS_VALUE) { + fusionState = COMPLETE; + return value; + } + return null; + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & ASYNC) != 0 && (requestedMode & THREAD_BARRIER) == 0) { + fusionState = NO_VALUE; + return ASYNC; + } + return NONE; + } + + @Override + public int size() { + return isEmpty() ? 0 : 1; + } + + void setMainFuture(Disposable c) { + for (; ; ) { + Disposable a = mainFuture; + if (a == OperatorDisposables.DISPOSED) { + c.dispose(); + return; + } + if (MAIN_FUTURE.compareAndSet(this, a, c)) { + return; + } + } + } + + void setRequestFuture(Disposable c) { + for (; ; ) { + Disposable a = requestFuture; + if (a == OperatorDisposables.DISPOSED) { + c.dispose(); + return; + } + if (REQUEST_FUTURE.compareAndSet(this, a, c)) { + return; + } + } + } + + @Override + public void run() { + T v; + + try { + v = callable.call(); + } + catch (Throwable ex) { + actual.onError(Operators.onOperatorError(this, ex, + actual.currentContext())); + return; + } + + if (v == null) { + fusionState = COMPLETE; + actual.onComplete(); + return; + } + + for (; ; ) { + int s = state; + if (s == HAS_CANCELLED || s == HAS_REQUEST_HAS_VALUE || s == NO_REQUEST_HAS_VALUE) { + return; + } + if (s == HAS_REQUEST_NO_VALUE) { + if (fusionState == NO_VALUE) { + this.value = v; + this.fusionState = HAS_VALUE; + } + actual.onNext(v); + if (state != HAS_CANCELLED) { + actual.onComplete(); + } + return; + } + this.value = v; + if (STATE.compareAndSet(this, s, NO_REQUEST_HAS_VALUE)) { + return; + } + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + for (; ; ) { + int s = state; + if (s == HAS_CANCELLED || s == HAS_REQUEST_NO_VALUE || s == HAS_REQUEST_HAS_VALUE) { + return; + } + if (s == NO_REQUEST_HAS_VALUE) { + if (STATE.compareAndSet(this, s, HAS_REQUEST_HAS_VALUE)) { + try { + Disposable f = scheduler.schedule(this::emitValue); + setRequestFuture(f); + } + catch (RejectedExecutionException ree) { + actual.onError(Operators.onRejectedExecution(ree, + actual.currentContext())); + } + } + return; + } + if (STATE.compareAndSet(this, s, HAS_REQUEST_NO_VALUE)) { + return; + } + } + } + } + + void emitValue() { + if (fusionState == NO_VALUE) { + this.fusionState = HAS_VALUE; + } + T v = value; + clear(); + if (v != null) { + actual.onNext(v); + } + if (state != HAS_CANCELLED) { + actual.onComplete(); + } + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOnValue.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOnValue.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSubscribeOnValue.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +/** + * Publisher indicating a scalar/empty source that subscribes on the specified scheduler. + * + * @param + * + * @see https://github.com/reactor/reactive-streams-commons + */ +final class FluxSubscribeOnValue extends Flux implements Fuseable, Scannable { + + final T value; + + final Scheduler scheduler; + + FluxSubscribeOnValue(@Nullable T value, Scheduler scheduler) { + this.value = value; + this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + T v = value; + if (v == null) { + ScheduledEmpty parent = new ScheduledEmpty(actual); + actual.onSubscribe(parent); + try { + parent.setFuture(scheduler.schedule(parent)); + } + catch (RejectedExecutionException ree) { + if (parent.future != OperatorDisposables.DISPOSED) { + actual.onError(Operators.onRejectedExecution(ree, + actual.currentContext())); + } + } + } + else { + actual.onSubscribe(new ScheduledScalar<>(actual, v, scheduler)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return null; + } + + static final class ScheduledScalar + implements QueueSubscription, InnerProducer, Runnable { + + final CoreSubscriber actual; + + final T value; + + final Scheduler scheduler; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(ScheduledScalar.class, "once"); + + volatile Disposable future; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater FUTURE = + AtomicReferenceFieldUpdater.newUpdater(ScheduledScalar.class, + Disposable.class, + "future"); + + static final Disposable FINISHED = Disposables.disposed(); + + int fusionState; + + static final int NO_VALUE = 1; + static final int HAS_VALUE = 2; + static final int COMPLETE = 3; + + ScheduledScalar(CoreSubscriber actual, T value, Scheduler scheduler) { + this.actual = actual; + this.value = value; + this.scheduler = scheduler; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Scannable.Attr key) { + if (key == Attr.CANCELLED) { + return future == OperatorDisposables.DISPOSED; + } + if (key == Attr.TERMINATED) { + return future == FINISHED; + } + if (key == Attr.BUFFERED) { + return 1; + } + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (ONCE.compareAndSet(this, 0, 1)) { + try { + Disposable f = scheduler.schedule(this); + if (!FUTURE.compareAndSet(this, + null, + f) && future != FINISHED && future != OperatorDisposables.DISPOSED) { + f.dispose(); + } + } + catch (RejectedExecutionException ree) { + if (future != FINISHED && future != OperatorDisposables.DISPOSED) { + actual.onError(Operators.onRejectedExecution(ree, + this, + null, + value, actual.currentContext())); + } + } + } + } + } + + @Override + public void cancel() { + ONCE.lazySet(this, 1); + Disposable f = future; + if (f != OperatorDisposables.DISPOSED && future != FINISHED) { + f = FUTURE.getAndSet(this, OperatorDisposables.DISPOSED); + if (f != null && f != OperatorDisposables.DISPOSED && f != FINISHED) { + f.dispose(); + } + } + } + + @Override + public void run() { + try { + if (fusionState == NO_VALUE) { + fusionState = HAS_VALUE; + } + actual.onNext(value); + actual.onComplete(); + } + finally { + FUTURE.lazySet(this, FINISHED); + } + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & Fuseable.ASYNC) != 0) { + fusionState = NO_VALUE; + return Fuseable.ASYNC; + } + return Fuseable.NONE; + } + + @Override + @Nullable + public T poll() { + if (fusionState == HAS_VALUE) { + fusionState = COMPLETE; + return value; + } + return null; + } + + @Override + public boolean isEmpty() { + return fusionState != HAS_VALUE; + } + + @Override + public int size() { + return isEmpty() ? 0 : 1; + } + + @Override + public void clear() { + fusionState = COMPLETE; + } + } + + static final class ScheduledEmpty implements QueueSubscription, Runnable { + + final Subscriber actual; + + volatile Disposable future; + static final AtomicReferenceFieldUpdater FUTURE = + AtomicReferenceFieldUpdater.newUpdater(ScheduledEmpty.class, + Disposable.class, + "future"); + + static final Disposable FINISHED = Disposables.disposed(); + + ScheduledEmpty(Subscriber actual) { + this.actual = actual; + } + + @Override + public void request(long n) { + Operators.validate(n); + } + + @Override + public void cancel() { + Disposable f = future; + if (f != OperatorDisposables.DISPOSED && f != FINISHED) { + f = FUTURE.getAndSet(this, OperatorDisposables.DISPOSED); + if (f != null && f != OperatorDisposables.DISPOSED && f != FINISHED) { + f.dispose(); + } + } + } + + @Override + public void run() { + try { + actual.onComplete(); + } + finally { + FUTURE.lazySet(this, FINISHED); + } + } + + void setFuture(Disposable f) { + if (!FUTURE.compareAndSet(this, null, f)) { + Disposable a = future; + if (a != FINISHED && a != OperatorDisposables.DISPOSED) { + f.dispose(); + } + } + } + + @Override + public int requestFusion(int requestedMode) { + return requestedMode & Fuseable.ASYNC; + } + + @Override + @Nullable + public Void poll() { + return null; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public int size() { + return 0; + } + + @Override + public void clear() { + // nothing to do + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchIfEmpty.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchIfEmpty.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchIfEmpty.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; + +/** + * Switches to another source if the first source turns out to be empty. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxSwitchIfEmpty extends InternalFluxOperator { + + final Publisher other; + + FluxSwitchIfEmpty(Flux source, + Publisher other) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + SwitchIfEmptySubscriber parent = new SwitchIfEmptySubscriber<>(actual, other); + + actual.onSubscribe(parent); + + return parent; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SwitchIfEmptySubscriber + extends Operators.MultiSubscriptionSubscriber { + + final Publisher other; + + boolean once; + + SwitchIfEmptySubscriber(CoreSubscriber actual, + Publisher other) { + super(actual); + this.other = other; + } + + @Override + public void onNext(T t) { + if (!once) { + once = true; + } + + actual.onNext(t); + } + + @Override + public void onComplete() { + if (!once) { + once = true; + + other.subscribe(this); + } + else { + actual.onComplete(); + } + } + + @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/FluxSwitchMap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchMap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchMap.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,565 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +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; + +/** + * Switches to a new Publisher generated via a function whenever the upstream produces an + * item. + * + * @param the source value type + * @param the output value type + * + * @see Reactive-Streams-Commons + */ +final class FluxSwitchMap extends InternalFluxOperator { + + final Function> mapper; + + final Supplier> queueSupplier; + + final int prefetch; + + @SuppressWarnings("ConstantConditions") + static final SwitchMapInner CANCELLED_INNER = + new SwitchMapInner<>(null, 0, Long.MAX_VALUE); + + FluxSwitchMap(Flux source, + Function> mapper, + Supplier> queueSupplier, + int prefetch) { + super(source); + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + + prefetch); + } + this.mapper = Objects.requireNonNull(mapper, "mapper"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + this.prefetch = prefetch; + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + //for now switchMap doesn't support onErrorContinue, so the scalar version shouldn't either + if (FluxFlatMap.trySubscribeScalarMap(source, actual, mapper, false, false)) { + return null; + } + + return new SwitchMapMain(actual, + mapper, + queueSupplier.get(), prefetch); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SwitchMapMain implements InnerOperator { + + final Function> mapper; + + final Queue queue; + final BiPredicate queueBiAtomic; + final int prefetch; + final CoreSubscriber actual; + + Subscription s; + + volatile boolean done; + + volatile Throwable error; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(SwitchMapMain.class, + Throwable.class, + "error"); + + volatile boolean cancelled; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(SwitchMapMain.class, "once"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SwitchMapMain.class, "requested"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(SwitchMapMain.class, "wip"); + + volatile SwitchMapInner inner; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater INNER = + AtomicReferenceFieldUpdater.newUpdater(SwitchMapMain.class, + SwitchMapInner.class, + "inner"); + + volatile long index; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater INDEX = + AtomicLongFieldUpdater.newUpdater(SwitchMapMain.class, "index"); + + volatile int active; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ACTIVE = + AtomicIntegerFieldUpdater.newUpdater(SwitchMapMain.class, "active"); + + @SuppressWarnings("unchecked") + SwitchMapMain(CoreSubscriber actual, + Function> mapper, + Queue queue, + int prefetch) { + this.actual = actual; + this.mapper = mapper; + this.queue = queue; + this.prefetch = prefetch; + this.active = 1; + if(queue instanceof BiPredicate){ + this.queueBiAtomic = (BiPredicate) queue; + } + else { + this.queueBiAtomic = null; + } + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.ERROR) return error; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.BUFFERED) return queue.size(); + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(inner); + } + + @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; + } + + long idx = INDEX.incrementAndGet(this); + + SwitchMapInner si = inner; + if (si != null) { + si.deactivate(); + si.cancel(); + } + + Publisher p; + + try { + p = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null publisher"); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + + SwitchMapInner innerSubscriber = + new SwitchMapInner<>(this, prefetch, idx); + + if (INNER.compareAndSet(this, si, innerSubscriber)) { + ACTIVE.getAndIncrement(this); + p.subscribe(innerSubscriber); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + if (Exceptions.addThrowable(ERROR, this, t)) { + + if (ONCE.compareAndSet(this, 0, 1)) { + deactivate(); + } + + cancelInner(); + done = true; + drain(); + } + else { + Operators.onErrorDropped(t, actual.currentContext()); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + + if (ONCE.compareAndSet(this, 0, 1)) { + deactivate(); + } + + done = true; + drain(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + if (WIP.getAndIncrement(this) == 0) { + cancelAndCleanup(queue); + } + } + } + + void deactivate() { + ACTIVE.decrementAndGet(this); + } + + void cancelInner() { + SwitchMapInner si = INNER.getAndSet(this, CANCELLED_INNER); + if (si != null && si != CANCELLED_INNER) { + si.cancel(); + si.deactivate(); + } + } + + void cancelAndCleanup(Queue q) { + s.cancel(); + + cancelInner(); + + q.clear(); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + Subscriber a = actual; + Queue q = queue; + + int missed = 1; + + for (; ; ) { + + long r = requested; + long e = 0L; + + while (r != e) { + boolean d = active == 0; + + @SuppressWarnings("unchecked") SwitchMapInner si = + (SwitchMapInner) q.poll(); + + boolean empty = si == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + Object second; + + while ((second = q.poll()) == null) { + } + + if (index == si.index) { + + @SuppressWarnings("unchecked") R v = (R) second; + + a.onNext(v); + + si.requestOne(); + + e++; + } + } + + if (r == e) { + if (checkTerminated(active == 0, q.isEmpty(), a, q)) { + return; + } + } + + if (e != 0 && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue q) { + if (cancelled) { + cancelAndCleanup(q); + return true; + } + + if (d) { + Throwable e = Exceptions.terminate(ERROR, this); + if (e != null && e != Exceptions.TERMINATED) { + cancelAndCleanup(q); + + a.onError(e); + return true; + } + else if (empty) { + a.onComplete(); + return true; + } + + } + return false; + } + + void innerNext(SwitchMapInner inner, R value) { + if (queueBiAtomic != null) { + //the queue is a "BiQueue" from Reactor, test(A, B) actually does double insertion + queueBiAtomic.test(inner, value); + } + else { + //the queue is a regular queue, a bit more overhead to do double insertion + queue.offer(inner); + queue.offer(value); + } + drain(); + } + + void innerError(SwitchMapInner inner, Throwable e) { + if (Exceptions.addThrowable(ERROR, this, e)) { + s.cancel(); + + if (ONCE.compareAndSet(this, 0, 1)) { + deactivate(); + } + inner.deactivate(); + drain(); + } + else { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + void innerComplete(SwitchMapInner inner) { + inner.deactivate(); + drain(); + } + } + + static final class SwitchMapInner implements InnerConsumer, Subscription { + + final SwitchMapMain parent; + + final int prefetch; + + final int limit; + + final long index; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(SwitchMapInner.class, "once"); + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(SwitchMapInner.class, + Subscription.class, + "s"); + + int produced; + + SwitchMapInner(SwitchMapMain parent, int prefetch, long index) { + this.parent = parent; + this.prefetch = prefetch; + this.limit = Operators.unboundedOrLimit(prefetch); + this.index = index; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + Subscription a = this.s; + if (a == Operators.cancelledSubscription()) { + s.cancel(); + } + if (a != null) { + s.cancel(); + + Operators.reportSubscriptionSet(); + return; + } + + if (S.compareAndSet(this, null, s)) { + s.request(Operators.unboundedOrPrefetch(prefetch)); + return; + } + a = this.s; + if (a != Operators.cancelledSubscription()) { + s.cancel(); + + Operators.reportSubscriptionSet(); + } + } + + @Override + public void onNext(R t) { + parent.innerNext(this, t); + } + + @Override + public void onError(Throwable t) { + parent.innerError(this, t); + } + + @Override + public void onComplete() { + parent.innerComplete(this); + } + + void deactivate() { + if (ONCE.compareAndSet(this, 0, 1)) { + parent.deactivate(); + } + } + + void requestOne() { + int p = produced + 1; + if (p == limit) { + produced = 0; + s.request(p); + } + else { + produced = p; + } + } + + @Override + public void request(long n) { + long p = produced + n; + if (p >= limit) { + produced = 0; + s.request(p); + } + else { + produced = (int) p; + } + } + + @Override + public void cancel() { + Subscription a = s; + if (a != Operators.cancelledSubscription()) { + a = S.getAndSet(this, Operators.cancelledSubscription()); + if (a != null && a != Operators.cancelledSubscription()) { + a.cancel(); + } + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchMapNoPrefetch.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchMapNoPrefetch.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchMapNoPrefetch.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,910 @@ +/* + * Copyright (c) 2021 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.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.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Switches to a new Publisher generated via a function whenever the upstream produces an + * item. + * + * @param the source value type + * @param the output value type + * + * @see Reactive-Streams-Commons + */ +final class FluxSwitchMapNoPrefetch extends InternalFluxOperator { + + final Function> mapper; + + FluxSwitchMapNoPrefetch(Flux source, + Function> mapper) { + super(source); + this.mapper = Objects.requireNonNull(mapper, "mapper"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + //for now switchMap doesn't support onErrorContinue, so the scalar version shouldn't either + if (FluxFlatMap.trySubscribeScalarMap(source, actual, mapper, false, false)) { + return null; + } + + return new SwitchMapMain(actual, mapper); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SwitchMapMain implements InnerOperator { + + final Function> mapper; + final CoreSubscriber actual; + + Subscription s; + + boolean done; + + SwitchMapInner inner; + + volatile Throwable throwable; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater THROWABLE = + AtomicReferenceFieldUpdater.newUpdater(SwitchMapMain.class, Throwable.class, "throwable"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SwitchMapMain.class, "requested"); + + volatile long state; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater STATE = + AtomicLongFieldUpdater.newUpdater(SwitchMapMain.class, "state"); + + SwitchMapMain(CoreSubscriber actual, + Function> mapper) { + this.actual = actual; + this.mapper = mapper; + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + final long state = this.state; + if (key == Attr.CANCELLED) return !this.done && state == TERMINATED; + if (key == Attr.PARENT) return this.s; + if (key == Attr.TERMINATED) return this.done; + if (key == Attr.ERROR) return this.throwable; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return this.requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(this.inner); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + this.actual.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (this.done) { + Operators.onNextDropped(t, this.actual.currentContext()); + return; + } + + final SwitchMapInner si = this.inner; + final boolean hasInner = si != null; + + if (!hasInner) { + final SwitchMapInner nsi = new SwitchMapInner<>(this, this.actual, 0); + this.inner = nsi; + subscribeInner(t, nsi, 0); + return; + } + + final int nextIndex = si.index + 1; + final SwitchMapInner nsi = new SwitchMapInner<>(this, this.actual, nextIndex); + + this.inner = nsi; + si.nextInner = nsi; + si.nextElement = t; + + long state = incrementIndex(this); + if (state == TERMINATED) { + Operators.onDiscard(t, this.actual.currentContext()); + return; + } + + if (isInnerSubscribed(state)) { + si.cancelFromParent(); + + if (!isWip(state)) { + final long produced = si.produced; + if (produced > 0) { + si.produced = 0; + if (this.requested != Long.MAX_VALUE) { + si.requested = 0; + REQUESTED.addAndGet(this, -produced); + } + } + subscribeInner(t, nsi, nextIndex); + } + } + } + + void subscribeInner(T nextElement, SwitchMapInner nextInner, int nextIndex) { + final CoreSubscriber actual = this.actual; + final Context context = actual.currentContext(); + + while (nextInner.index != nextIndex) { + Operators.onDiscard(nextElement, context); + nextElement = nextInner.nextElement; + nextInner = nextInner.nextInner; + } + + Publisher p; + try { + p = Objects.requireNonNull(this.mapper.apply(nextElement), + "The mapper returned a null publisher"); + } + catch (Throwable e) { + onError(Operators.onOperatorError(this.s, e, nextElement, context)); + return; + } + + p.subscribe(nextInner); + } + + @Override + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, this.actual.currentContext()); + return; + } + + this.done = true; + + if (!Exceptions.addThrowable(THROWABLE, this, t)) { + Operators.onErrorDropped(t, this.actual.currentContext()); + return; + } + + final long state = setTerminated(this); + if (state == TERMINATED) { + return; + } + + final SwitchMapInner inner = this.inner; + if (inner != null && isInnerSubscribed(state)) { + inner.cancelFromParent(); + } + + //noinspection ConstantConditions + this.actual.onError(Exceptions.terminate(THROWABLE, this)); + } + + @Override + public void onComplete() { + if (this.done) { + return; + } + + this.done = true; + + final long state = setMainCompleted(this); + if (state == TERMINATED) { + return; + } + + final SwitchMapInner inner = this.inner; + if (inner == null || hasInnerCompleted(state)) { + this.actual.onComplete(); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + long previousRequested = Operators.addCap(REQUESTED, this, n); + long state = addRequest(this, previousRequested); + + if (state == TERMINATED) { + return; + } + + if (hasRequest(state) == 1 && isInnerSubscribed(state) && !hasInnerCompleted(state)) { + final SwitchMapInner inner = this.inner; + if (inner.index == index(state)) { + inner.request(n); + } + } + } + } + + @Override + public void cancel() { + final long state = setTerminated(this); + if (state == TERMINATED) { + return; + } + + final SwitchMapInner inner = this.inner; + if (inner != null && isInnerSubscribed(state) && !hasInnerCompleted(state) && inner.index == index(state)) { + inner.cancelFromParent(); + } + + if (!hasMainCompleted(state)) { + this.s.cancel(); + + //ensure that onError which races with cancel has its error dropped + final Throwable e = Exceptions.terminate(THROWABLE, this); + if (e != null) { + Operators.onErrorDropped(e, this.actual.currentContext()); + } + } + } + } + + static final class SwitchMapInner implements InnerConsumer { + + final SwitchMapMain parent; + final CoreSubscriber actual; + + final int index; + + Subscription s; + long produced; + long requested; + boolean done; + + T nextElement; + SwitchMapInner nextInner; + + SwitchMapInner(SwitchMapMain parent, CoreSubscriber actual, int index) { + this.parent = parent; + this.actual = actual; + this.index = index; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return isCancelledByParent(); + if (key == Attr.PARENT) return this.parent; + if (key == Attr.ACTUAL) return this.actual; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + final int expectedIndex = this.index; + final SwitchMapMain parent = this.parent; + final long state = setInnerSubscribed(parent, expectedIndex); + + if (state == TERMINATED) { + s.cancel(); + return; + } + + final int actualIndex = index(state); + if (expectedIndex != actualIndex) { + s.cancel(); + parent.subscribeInner(this.nextElement, this.nextInner, actualIndex); + return; + } + + if (hasRequest(state) > 0) { + long requested = parent.requested; + this.requested = requested; + + s.request(requested); + } + } + } + + @Override + public void onNext(R t) { + if (this.done) { + Operators.onNextDropped(t, this.actual.currentContext()); + return; + } + + final SwitchMapMain parent = this.parent; + final Subscription s = this.s; + final int expectedIndex = this.index; + + long requested = this.requested; + long state = setWip(parent, expectedIndex); + + if (state == TERMINATED) { + Operators.onDiscard(t, this.actual.currentContext()); + return; + } + + int actualIndex = index(state); + if (actualIndex != expectedIndex) { + Operators.onDiscard(t, this.actual.currentContext()); + return; + } + + this.actual.onNext(t); + + long produced = 0; + boolean isDemandFulfilled = false; + int expectedHasRequest = hasRequest(state); + + if (requested != Long.MAX_VALUE) { + produced = this.produced + 1; + this.produced = produced; + + if (expectedHasRequest > 1) { + long actualRequested = parent.requested; + long toRequestInAddition = actualRequested - requested; + if (toRequestInAddition > 0) { + requested = actualRequested; + this.requested = requested; + + if (requested == Long.MAX_VALUE) { + // we need to reset stats if unbounded requested + this.produced = produced = 0; + s.request(Long.MAX_VALUE); + } + else { + s.request(toRequestInAddition); + } + } + } + + isDemandFulfilled = produced == requested; + if (isDemandFulfilled) { + this.produced = 0; + requested = SwitchMapMain.REQUESTED.addAndGet(parent, -produced); + this.requested = requested; + + produced = 0; + isDemandFulfilled = requested == 0; + if (!isDemandFulfilled) { + s.request(requested); + } + } + } + + for (;;) { + state = unsetWip(parent, expectedIndex, isDemandFulfilled, expectedHasRequest); + + if (state == TERMINATED) { + return; + } + + actualIndex = index(state); + if (expectedIndex != actualIndex) { + if (produced > 0) { + this.produced = 0; + this.requested = 0; + + SwitchMapMain.REQUESTED.addAndGet(parent, -produced); + } + parent.subscribeInner(this.nextElement, this.nextInner, actualIndex); + return; + } + + int actualHasRequest = hasRequest(state); + if (isDemandFulfilled && expectedHasRequest < actualHasRequest) { + expectedHasRequest = actualHasRequest; + + long currentRequest = parent.requested; + long toRequestInAddition = currentRequest - requested; + if (toRequestInAddition > 0) { + requested = currentRequest; + this.requested = requested; + + isDemandFulfilled = false; + s.request(requested == Long.MAX_VALUE ? Long.MAX_VALUE : toRequestInAddition); + } + + continue; + } + + return; + } + } + + @Override + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, this.actual.currentContext()); + return; + } + + this.done = true; + + final SwitchMapMain parent = this.parent; + + // ensures that racing on setting error will pass smoothly + if (!Exceptions.addThrowable(SwitchMapMain.THROWABLE, parent, t)) { + Operators.onErrorDropped(t, this.actual.currentContext()); + return; + } + + final long state = setTerminated(parent); + if (state == TERMINATED) { + return; + } + + if (!hasMainCompleted(state)) { + parent.s.cancel(); + } + + //noinspection ConstantConditions + this.actual.onError(Exceptions.terminate(SwitchMapMain.THROWABLE, parent)); + } + + @Override + public void onComplete() { + if (this.done) { + return; + } + + this.done = true; + + final SwitchMapMain parent = this.parent; + final int expectedIndex = this.index; + + long state = setWip(parent, expectedIndex); + if (state == TERMINATED) { + return; + } + + int actualIndex = index(state); + if (actualIndex != expectedIndex) { + return; + } + + final long produced = this.produced; + if (produced > 0) { + this.produced = 0; + this.requested = 0; + SwitchMapMain.REQUESTED.addAndGet(parent, -produced); + } + + if (hasMainCompleted(state)) { + this.actual.onComplete(); + return; + } + + state = setInnerCompleted(parent); + if (state == TERMINATED) { + return; + } + + actualIndex = index(state); + if (expectedIndex != actualIndex) { + parent.subscribeInner(this.nextElement, this.nextInner, actualIndex); + } else if (hasMainCompleted(state)) { + this.actual.onComplete(); + } + } + + void request(long n) { + long requested = this.requested; + this.requested = Operators.addCap(requested, n); + + this.s.request(n); + } + + boolean isCancelledByParent() { + long state = parent.state; + return (this.index != index(state) && !this.done) || (!parent.done && state == TERMINATED); + } + + void cancelFromParent() { + this.s.cancel(); + } + } + + static int INDEX_OFFSET = 32; + static int HAS_REQUEST_OFFSET = 4; + static long TERMINATED = + 0b11111111111111111111111111111111_1111111111111111111111111111_1_1_1_1L; + static long INNER_WIP_MASK = + 0b00000000000000000000000000000000_0000000000000000000000000000_0_0_0_1L; + static long INNER_SUBSCRIBED_MASK = + 0b00000000000000000000000000000000_0000000000000000000000000000_0_0_1_0L; + static long INNER_COMPLETED_MASK = + 0b00000000000000000000000000000000_0000000000000000000000000000_0_1_0_0L; + static long COMPLETED_MASK = + 0b00000000000000000000000000000000_0000000000000000000000000000_1_0_0_0L; + static long HAS_REQUEST_MASK = + 0b00000000000000000000000000000000_1111111111111111111111111111_0_0_0_0L; + static int MAX_HAS_REQUEST = 0b0000_1111111111111111111111111111; + + /** + * Atomically set state as terminated + * + * @return previous state + */ + static long setTerminated(SwitchMapMain instance) { + for (;;) { + final long state = instance.state; + + // do nothing if stream is terminated + if (state == TERMINATED) { + return TERMINATED; + } + + if (SwitchMapMain.STATE.compareAndSet(instance, state, TERMINATED)) { + return state; + } + } + } + + /** + * Atomically set main completed flag. + * + * The flag will not be set if the current state is {@link #TERMINATED} + * + * @return previous state + */ + static long setMainCompleted(SwitchMapMain instance) { + for (; ; ) { + final long state = instance.state; + + // do nothing if stream is terminated + if (state == TERMINATED) { + return TERMINATED; + } + + if ((state & COMPLETED_MASK) == COMPLETED_MASK) { + return state; + } + + if (SwitchMapMain.STATE.compareAndSet(instance, state, state | COMPLETED_MASK)) { + return state; + } + } + } + + /** + * Atomically add increment hasRequest value to indicate that we added capacity to + * {@link SwitchMapMain#requested} + * + *

+ * Note, If the given {@code previousRequested} param value is greater than zero it + * works as an indicator that in some scenarios prevents setting HasRequest to 1. + * Due to racing nature, the {@link SwitchMapMain#requested} may be incremented but + * the following addRequest may be delayed. That will lead that Inner may + * read the new value and request more data from the inner upstream. In turn, the + * delay may be that big that inner may fulfill new demand and set hasRequest to 0 + * faster that addRequest happens and leave {@link SwitchMapMain#requested} at + * zero as well. Thus, we use previousRequested to check if inner was requested, + * and if it was, we assume the explained case happened and newly added demand was + * already fulfilled + *

+ * + * @param previousRequested received from {@link Operators#addCap} method. + * @return next state + */ + static long addRequest(SwitchMapMain instance, long previousRequested) { + for (;;) { + long state = instance.state; + + // do nothing if stream is terminated + if (state == TERMINATED) { + return TERMINATED; + } + + final int hasRequest = hasRequest(state); + // if the current hasRequest is zero and previousRequested is greater than + // zero, it means that Inner was faster then this operation and has already + // fulfilled the demand. In that case we do not have to increment + // hasRequest and just need to return + if (hasRequest == 0 && previousRequested > 0) { + return state; + } + + final long nextState = state(index(state), // keep index as is + isWip(state), // keep wip flag as is + hasRequest + 1, // increment hasRequest by 1 + isInnerSubscribed(state), // keep inner subscribed flag as is + hasMainCompleted(state), // keep main completed flag as is + hasInnerCompleted(state)); // keep inner completed flag as is + if (SwitchMapMain.STATE.compareAndSet(instance, state, nextState)) { + return nextState; + } + } + } + + /** + * Atomically increment the index of the active inner subscriber. + * + * The index will not be changed if the current state is {@link #TERMINATED} + * + * @return previous state + */ + static long incrementIndex(SwitchMapMain instance) { + long state = instance.state; + + // do nothing if stream is terminated + if (state == TERMINATED) { + return TERMINATED; + } + + // generate next index once. Main#OnNext will never race with another Main#OnNext. Also, + // this method is only called from Main#Onnext + 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 + return state; + } + + state = instance.state; + + // do nothing if stream is terminated + if (state == TERMINATED) { + return TERMINATED; + } + } + } + + /** + * Atomically set a bit on the current state indicating that current inner has + * received a subscription. + * + * If the index has changed during the subscription or stream was cancelled, the bit + * will not bit set + * + * @return previous state + */ + static long setInnerSubscribed(SwitchMapMain instance, int expectedIndex) { + for (;;) { + final long state = instance.state; + + // do nothing if stream is terminated + if (state == TERMINATED) { + return TERMINATED; + } + + int actualIndex = index(state); + // do nothing if index has changed before Subscription was received + if (expectedIndex != actualIndex) { + return state; + } + + 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 + return state; + } + } + } + + /** + * Atomically set WIP flag to indicate that we do some work from Inner#OnNext or + * Inner#OnComplete. + * + * If the current state is {@link #TERMINATED} or the index is + * one equals to expected then the flag will not be set + * + * @return previous state + */ + static long setWip(SwitchMapMain instance, int expectedIndex) { + for (;;) { + final long state = instance.state; + + // do nothing if stream is terminated + if (state == TERMINATED) { + return TERMINATED; + } + + int actualIndex = index(state); + // do nothing if index has changed before Subscription was received + if (expectedIndex != actualIndex) { + 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 + return state; + } + } + } + + /** + * Atomically unset WIP flag to indicate that we have done the work from + * Inner#OnNext + * + * If the current state is {@link #TERMINATED} or the index is + * one equals to expected then the flag will not be set + * + * @return previous state + */ + static long unsetWip(SwitchMapMain instance, + int expectedIndex, + boolean isDemandFulfilled, + int expectedRequest) { + for (; ; ) { + final long state = instance.state; + + // do nothing if stream is terminated + if (state == TERMINATED) { + return TERMINATED; + } + + final int actualIndex = index(state); + final int actualRequest = hasRequest(state); + final boolean sameIndex = expectedIndex == actualIndex; + // if during onNext we fulfilled the requested demand and index has not + // changed and it is observed that downstream requested more demand, we + // have to repeat and see of we can request more from the inner upstream. + // We dont have to chang WIP if we have isDemandFulfilled set to true + if (isDemandFulfilled && expectedRequest < actualRequest && sameIndex) { + 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 + return state; + } + } + } + + /** + * Atomically set the inner completed flag. + * + * The flag will not be set if the current state is {@link #TERMINATED} + * + * @return previous state + */ + static long setInnerCompleted(SwitchMapMain instance) { + for (; ; ) { + final long state = instance.state; + + // do nothing if stream is terminated + if (state == TERMINATED) { + return TERMINATED; + } + + 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 + return state; + } + } + } + + /** + * Generic method to encode state from the given params into a long value + * + * @return encoded state + */ + static long state(int index, boolean wip, int hasRequest, boolean innerSubscribed, + boolean mainCompleted, boolean innerCompleted) { + return ((long) index << INDEX_OFFSET) + | (wip ? INNER_WIP_MASK : 0) + | ((long) Math.max(Math.min(hasRequest, MAX_HAS_REQUEST), 0) << HAS_REQUEST_OFFSET) + | (innerSubscribed ? INNER_SUBSCRIBED_MASK : 0) + | (mainCompleted ? COMPLETED_MASK : 0) + | (innerCompleted ? INNER_COMPLETED_MASK : 0); + } + + static boolean isInnerSubscribed(long state) { + return (state & INNER_SUBSCRIBED_MASK) == INNER_SUBSCRIBED_MASK; + } + + static boolean hasMainCompleted(long state) { + return (state & COMPLETED_MASK) == COMPLETED_MASK; + } + + static boolean hasInnerCompleted(long state) { + return (state & INNER_COMPLETED_MASK) == INNER_COMPLETED_MASK; + } + + static int hasRequest(long state) { + return (int) (state & HAS_REQUEST_MASK) >> HAS_REQUEST_OFFSET; + } + + static int index(long state) { + return (int) (state >>> INDEX_OFFSET); + } + + static int nextIndex(long state) { + return ((int) (state >>> INDEX_OFFSET)) + 1; + } + + static boolean isWip(long state) { + return (state & INNER_WIP_MASK) == INNER_WIP_MASK; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchOnFirst.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchOnFirst.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxSwitchOnFirst.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,972 @@ +/* + * Copyright (c) 2018-2021 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.CancellationException; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.function.BiFunction; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * @param + * @param + * @author Oleh Dokuka + */ +final class FluxSwitchOnFirst extends InternalFluxOperator { + + final BiFunction, Flux, Publisher> transformer; + final boolean cancelSourceOnComplete; + + FluxSwitchOnFirst(Flux source, + BiFunction, Flux, Publisher> transformer, + boolean cancelSourceOnComplete) { + super(source); + this.transformer = Objects.requireNonNull(transformer, "transformer"); + this.cancelSourceOnComplete = cancelSourceOnComplete; + } + + @Override + public int getPrefetch() { + return 1; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof Fuseable.ConditionalSubscriber) { + return new SwitchOnFirstConditionalMain<>((Fuseable.ConditionalSubscriber) actual, + transformer, + cancelSourceOnComplete); + } + return new SwitchOnFirstMain<>(actual, transformer, cancelSourceOnComplete); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return super.scanUnsafe(key); + } + + static final int HAS_FIRST_VALUE_RECEIVED_FLAG = + 0b0000_0000_0000_0000_0000_0000_0000_0001; + static final int HAS_INBOUND_SUBSCRIBED_ONCE_FLAG = + 0b0000_0000_0000_0000_0000_0000_0000_0010; + static final int HAS_INBOUND_SUBSCRIBER_SET_FLAG = + 0b0000_0000_0000_0000_0000_0000_0000_0100; + static final int HAS_INBOUND_REQUESTED_ONCE_FLAG = + 0b0000_0000_0000_0000_0000_0000_0000_1000; + static final int HAS_FIRST_VALUE_SENT_FLAG = + 0b0000_0000_0000_0000_0000_0000_0001_0000; + static final int HAS_INBOUND_CANCELLED_FLAG = + 0b0000_0000_0000_0000_0000_0000_0010_0000; + static final int HAS_INBOUND_CLOSED_PREMATURELY_FLAG = + 0b0000_0000_0000_0000_0000_0000_0100_0000; + static final int HAS_INBOUND_TERMINATED_FLAG = + 0b0000_0000_0000_0000_0000_0000_1000_0000; + + static final int HAS_OUTBOUND_SUBSCRIBED_FLAG = + 0b0000_0000_0000_0000_0000_0001_0000_0000; + static final int HAS_OUTBOUND_CANCELLED_FLAG = + 0b0000_0000_0000_0000_0000_0010_0000_0000; + static final int HAS_OUTBOUND_TERMINATED_FLAG = + 0b0000_0000_0000_0000_0000_0100_0000_0000; + + /** + * Adds a flag which indicate that the first inbound onNext signal has already been + * received. Fails if inbound is cancelled. + * + * @return previous observed state + */ + static long markFirstValueReceived(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasInboundCancelled(state) || hasInboundClosedPrematurely(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_FIRST_VALUE_RECEIVED_FLAG)) { + return state; + } + } + } + + /** + * Adds a flag which indicate that the inbound has already been subscribed once. + * Fails if inbound has already been subscribed once. + * + * @return previous observed state + */ + static long markInboundSubscribedOnce(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasInboundSubscribedOnce(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_SUBSCRIBED_ONCE_FLAG)) { + return state; + } + } + } + + /** + * Adds a flag which indicate that the inbound subscriber has already been set. + * Fails if inbound is cancelled. + * + * @return previous observed state + */ + static long markInboundSubscriberSet(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasInboundCancelled(state) || hasInboundClosedPrematurely(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_SUBSCRIBER_SET_FLAG)) { + return state; + } + } + } + + /** + * Adds a flag which indicate that the inbound has already been requested once. + * Fails if inbound is cancelled. + * + * @return previous observed state + */ + static long markInboundRequestedOnce(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasInboundCancelled(state) || hasInboundClosedPrematurely(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_REQUESTED_ONCE_FLAG)) { + return state; + } + } + } + + /** + * Adds a flag which indicate that the first onNext value has been successfully + * delivered. Fails if inbound is cancelled. + * + * @return previous observed state + */ + static long markFirstValueSent(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasInboundCancelled(state) || hasInboundClosedPrematurely(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_FIRST_VALUE_SENT_FLAG)) { + return state; + } + } + } + + /** + * Adds a flag which indicate that the inbound has already been terminated with + * onComplete or onError. Fails if inbound is cancelled. + * + * @return previous observed state + */ + static long markInboundTerminated(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasInboundCancelled(state) || hasInboundClosedPrematurely(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_TERMINATED_FLAG)) { + return state; + } + } + } + + /** + * Adds a flag which indicate that the inbound has already been cancelled. Fails if + * inbound is cancelled. + * + * @return previous observed state + */ + static long markInboundCancelled(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasInboundCancelled(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_CANCELLED_FLAG)) { + return state; + } + } + } + + /** + * Adds flags which indicate that the inbound has prematurely from the very bottom + * of the pipe. Fails if either inbound is cancelled or terminated before. + * + * @return previous observed state + */ + static long markInboundClosedPrematurely(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasInboundTerminated(state) || hasInboundCancelled(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_CLOSED_PREMATURELY_FLAG)) { + return state; + } + } + } + + /** + * Adds flags which indicate that the inbound has cancelled upstream and errored + * the outbound downstream. Fails if either inbound is cancelled or outbound is + * cancelled. + * + * Note, this is a rare case needed to add cancellation for inbound and + * termination for outbound and can only be occurred during the onNext calling + * transformation function which then throws unexpected error. + * + * @return previous observed state + */ + static long markInboundCancelledAndOutboundTerminated(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasInboundCancelled(state) || hasOutboundCancelled(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_INBOUND_CANCELLED_FLAG | HAS_OUTBOUND_TERMINATED_FLAG)) { + return state; + } + } + } + + /** + * Adds a flag which indicate that the outbound has received subscription. Fails if + * outbound is cancelled. + * + * @return previous observed state + */ + static long markOutboundSubscribed(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasOutboundCancelled(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_OUTBOUND_SUBSCRIBED_FLAG)) { + return state; + } + } + } + + + /** + * Adds a flag which indicate that the outbound has already been + * terminated with onComplete or onError. Fails if outbound is cancelled or terminated. + * + * @return previous observed state + */ + static long markOutboundTerminated(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasOutboundCancelled(state) || hasOutboundTerminated(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_OUTBOUND_TERMINATED_FLAG)) { + return state; + } + } + } + + /** + * Adds a flag which indicate that the outbound has already been cancelled. Fails + * if outbound is cancelled or terminated. + * + * @return previous observed state + */ + static long markOutboundCancelled(AbstractSwitchOnFirstMain instance) { + for (;;) { + final int state = instance.state; + + if (hasOutboundTerminated(state) || hasOutboundCancelled(state)) { + return state; + } + + if (AbstractSwitchOnFirstMain.STATE.compareAndSet(instance, state, state | HAS_OUTBOUND_CANCELLED_FLAG)) { + return state; + } + } + } + + static boolean hasInboundCancelled(long state) { + return (state & HAS_INBOUND_CANCELLED_FLAG) == HAS_INBOUND_CANCELLED_FLAG; + } + + static boolean hasInboundClosedPrematurely(long state) { + return (state & HAS_INBOUND_CLOSED_PREMATURELY_FLAG) == HAS_INBOUND_CLOSED_PREMATURELY_FLAG; + } + + static boolean hasInboundTerminated(long state) { + return (state & HAS_INBOUND_TERMINATED_FLAG) == HAS_INBOUND_TERMINATED_FLAG; + } + + static boolean hasFirstValueReceived(long state) { + return (state & HAS_FIRST_VALUE_RECEIVED_FLAG) == HAS_FIRST_VALUE_RECEIVED_FLAG; + } + + static boolean hasFirstValueSent(long state) { + return (state & HAS_FIRST_VALUE_SENT_FLAG) == HAS_FIRST_VALUE_SENT_FLAG; + } + + static boolean hasInboundSubscribedOnce(long state) { + return (state & HAS_INBOUND_SUBSCRIBED_ONCE_FLAG) == HAS_INBOUND_SUBSCRIBED_ONCE_FLAG; + } + + static boolean hasInboundSubscriberSet(long state) { + return (state & HAS_INBOUND_SUBSCRIBER_SET_FLAG) == HAS_INBOUND_SUBSCRIBER_SET_FLAG; + } + + static boolean hasInboundRequestedOnce(long state) { + return (state & HAS_INBOUND_REQUESTED_ONCE_FLAG) == HAS_INBOUND_REQUESTED_ONCE_FLAG; + } + + static boolean hasOutboundSubscribed(long state) { + return (state & HAS_OUTBOUND_SUBSCRIBED_FLAG) == HAS_OUTBOUND_SUBSCRIBED_FLAG; + } + + static boolean hasOutboundCancelled(long state) { + return (state & HAS_OUTBOUND_CANCELLED_FLAG) == HAS_OUTBOUND_CANCELLED_FLAG; + } + + static boolean hasOutboundTerminated(long state) { + return (state & HAS_OUTBOUND_TERMINATED_FLAG) == HAS_OUTBOUND_TERMINATED_FLAG; + } + + static abstract class AbstractSwitchOnFirstMain extends Flux + implements InnerOperator { + + final SwitchOnFirstControlSubscriber outboundSubscriber; + final BiFunction, Flux, Publisher> transformer; + + Subscription s; + + boolean isInboundRequestedOnce; + boolean isFirstOnNextReceivedOnce; + T firstValue; + + Throwable throwable; + boolean done; + + CoreSubscriber inboundSubscriber; + + volatile int state; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(AbstractSwitchOnFirstMain.class, "state"); + + @SuppressWarnings("unchecked") + AbstractSwitchOnFirstMain(CoreSubscriber outboundSubscriber, + BiFunction, Flux, Publisher> transformer, + boolean cancelSourceOnComplete) { + this.outboundSubscriber = outboundSubscriber instanceof Fuseable.ConditionalSubscriber ? + new SwitchOnFirstConditionalControlSubscriber<>(this, + (Fuseable.ConditionalSubscriber) outboundSubscriber, + cancelSourceOnComplete) : + new SwitchOnFirstControlSubscriber<>(this, outboundSubscriber, + cancelSourceOnComplete); + this.transformer = transformer; + } + + @Override + @Nullable + public final Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return hasInboundCancelled(this.state) || hasInboundClosedPrematurely(this.state); + if (key == Attr.TERMINATED) return hasInboundTerminated(this.state) || hasInboundClosedPrematurely(this.state); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public final CoreSubscriber actual() { + return this.outboundSubscriber; + } + + @Override + public final void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + this.outboundSubscriber.sendSubscription(); + if (!hasInboundCancelled(this.state)) { + s.request(1); + } + } + } + + @Override + public final void onNext(T t) { + if (this.done) { + Operators.onNextDropped(t, currentContext()); + return; + } + + if (!this.isFirstOnNextReceivedOnce) { + this.isFirstOnNextReceivedOnce = true; + this.firstValue = t; + + long previousState = markFirstValueReceived(this); + if (hasInboundCancelled(previousState) || hasInboundClosedPrematurely(previousState)) { + this.firstValue = null; + Operators.onDiscard(t, this.outboundSubscriber.currentContext()); + return; + } + + final Publisher outboundPublisher; + final SwitchOnFirstControlSubscriber o = this.outboundSubscriber; + + try { + final Signal signal = Signal.next(t, o.currentContext()); + outboundPublisher = Objects.requireNonNull(this.transformer.apply(signal, this), "The transformer returned a null value"); + } + catch (Throwable e) { + this.done = true; + + previousState = markInboundCancelledAndOutboundTerminated(this); + if (hasInboundCancelled(previousState) || hasOutboundCancelled(previousState)) { + Operators.onErrorDropped(e, o.currentContext()); + return; + } + + this.firstValue = null; + Operators.onDiscard(t, o.currentContext()); + + o.errorDirectly(Operators.onOperatorError(this.s, e, t, o.currentContext())); + return; + } + + outboundPublisher.subscribe(o); + return; + } + + synchronized (this) { + this.inboundSubscriber.onNext(t); + } + } + + @Override + public final void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, this.outboundSubscriber.currentContext()); + return; + } + + this.done = true; + this.throwable = t; + + final long previousState = markInboundTerminated(this); + if (hasInboundCancelled(previousState) || hasInboundTerminated(previousState) || hasInboundClosedPrematurely(previousState)) { + Operators.onErrorDropped(t, this.outboundSubscriber.currentContext()); + return; + } + + if (hasFirstValueSent(previousState)) { + synchronized (this) { + this.inboundSubscriber.onError(t); + } + return; + } + + if (!hasFirstValueReceived(previousState)) { + final Publisher result; + final CoreSubscriber o = this.outboundSubscriber; + try { + final Signal signal = Signal.error(t, o.currentContext()); + result = Objects.requireNonNull(this.transformer.apply(signal, this), "The transformer returned a null value"); + } + catch (Throwable e) { + o.onError(Exceptions.addSuppressed(t, e)); + return; + } + + result.subscribe(o); + } + } + + @Override + public final void onComplete() { + if (this.done) { + return; + } + + this.done = true; + + final long previousState = markInboundTerminated(this); + if (hasInboundCancelled(previousState) || hasInboundTerminated(previousState) || hasInboundClosedPrematurely(previousState)) { + return; + } + + if (hasFirstValueSent(previousState)) { + synchronized (this) { + this.inboundSubscriber.onComplete(); + } + return; + } + + if (!hasFirstValueReceived(previousState)) { + final Publisher result; + final CoreSubscriber o = this.outboundSubscriber; + + try { + final Signal signal = Signal.complete(o.currentContext()); + result = Objects.requireNonNull(this.transformer.apply(signal, this), "The transformer returned a null value"); + } + catch (Throwable e) { + o.onError(e); + return; + } + + result.subscribe(o); + } + } + + @Override + public final void cancel() { + long previousState = markInboundCancelled(this); + if (hasInboundCancelled(previousState) || hasInboundTerminated(previousState) || hasInboundClosedPrematurely(previousState)) { + return; + } + + this.s.cancel(); + + if (hasFirstValueReceived(previousState) && !hasInboundRequestedOnce(previousState)) { + final T f = this.firstValue; + this.firstValue = null; + Operators.onDiscard(f, currentContext()); + } + } + + final void cancelAndError() { + long previousState = markInboundClosedPrematurely(this); + if (hasInboundCancelled(previousState) || hasInboundTerminated(previousState)) { + return; + } + + this.s.cancel(); + + if (hasFirstValueReceived(previousState) && !hasFirstValueSent(previousState)) { + if (!hasInboundRequestedOnce(previousState)) { + final T f = this.firstValue; + this.firstValue = null; + Operators.onDiscard(f, currentContext()); + + if (hasInboundSubscriberSet(previousState)) { + this.inboundSubscriber.onError(new CancellationException( + "FluxSwitchOnFirst has already been cancelled")); + } + } + return; + } + + if (hasInboundSubscriberSet(previousState)) { + synchronized (this) { + this.inboundSubscriber.onError(new CancellationException("FluxSwitchOnFirst has already been cancelled")); + } + } + } + + @Override + public final void request(long n) { + if (Operators.validate(n)) { + // This is a sanity check to avoid extra volatile read in the request + // context + if (!this.isInboundRequestedOnce) { + this.isInboundRequestedOnce = true; + + if (this.isFirstOnNextReceivedOnce) { + final long previousState = markInboundRequestedOnce(this); + if (hasInboundCancelled(previousState) || hasInboundClosedPrematurely(previousState)) { + return; + } + + final T first = this.firstValue; + this.firstValue = null; + + final boolean wasDelivered = sendFirst(first); + if (wasDelivered) { + if (n != Long.MAX_VALUE) { + if (--n > 0) { + this.s.request(n); + } + return; + } + } + } + } + + this.s.request(n); + } + } + + @Override + public final void subscribe(CoreSubscriber inboundSubscriber) { + long previousState = markInboundSubscribedOnce(this); + if (hasInboundSubscribedOnce(previousState)) { + Operators.error(inboundSubscriber, new IllegalStateException("FluxSwitchOnFirst allows only one Subscriber")); + return; + } + + if (hasInboundClosedPrematurely(previousState)) { + Operators.error(inboundSubscriber, new CancellationException("FluxSwitchOnFirst has already been cancelled")); + return; + } + + if (!hasFirstValueReceived(previousState)) { + final Throwable t = this.throwable; + if (t != null) { + Operators.error(inboundSubscriber, t); + } + else { + Operators.complete(inboundSubscriber); + } + return; + } + + this.inboundSubscriber = convert(inboundSubscriber); + + inboundSubscriber.onSubscribe(this); + + previousState = markInboundSubscriberSet(this); + if (hasInboundClosedPrematurely(previousState) + && (!hasInboundRequestedOnce(previousState) || hasFirstValueSent(previousState)) + && !hasInboundCancelled(previousState)) { + inboundSubscriber.onError(new CancellationException("FluxSwitchOnFirst has already been cancelled")); + } + } + + abstract CoreSubscriber convert(CoreSubscriber inboundSubscriber); + + final boolean sendFirst(T firstValue) { + final CoreSubscriber a = this.inboundSubscriber; + + final boolean sent = tryDirectSend(a, firstValue); + + final long previousState = markFirstValueSent(this); + if (hasInboundCancelled(previousState)) { + return sent; + } + + if (hasInboundClosedPrematurely(previousState)) { + a.onError(new CancellationException("FluxSwitchOnFirst has already been cancelled")); + return sent; + } + + if (hasInboundTerminated(previousState)) { + Throwable t = this.throwable; + if (t != null) { + a.onError(t); + } + else { + a.onComplete(); + } + } + + return sent; + } + + abstract boolean tryDirectSend(CoreSubscriber actual, T value); + + } + + static final class SwitchOnFirstMain extends AbstractSwitchOnFirstMain { + + SwitchOnFirstMain(CoreSubscriber outer, + BiFunction, Flux, Publisher> transformer, + boolean cancelSourceOnComplete) { + super(outer, transformer, cancelSourceOnComplete); + } + + @Override + CoreSubscriber convert(CoreSubscriber inboundSubscriber) { + return inboundSubscriber; + } + + @Override + boolean tryDirectSend(CoreSubscriber actual, T t) { + actual.onNext(t); + return true; + } + } + + static final class SwitchOnFirstConditionalMain + extends AbstractSwitchOnFirstMain + implements Fuseable.ConditionalSubscriber { + + SwitchOnFirstConditionalMain(Fuseable.ConditionalSubscriber outer, + BiFunction, Flux, Publisher> transformer, + boolean cancelSourceOnComplete) { + super(outer, transformer, cancelSourceOnComplete); + } + + @Override + CoreSubscriber convert(CoreSubscriber inboundSubscriber) { + return Operators.toConditionalSubscriber(inboundSubscriber); + } + + @Override + @SuppressWarnings("unchecked") + public boolean tryOnNext(T t) { + if (this.done) { + Operators.onNextDropped(t, currentContext()); + return false; + } + + if (!this.isFirstOnNextReceivedOnce) { + this.isFirstOnNextReceivedOnce = true; + this.firstValue = t; + + long previousState = markFirstValueReceived(this); + if (hasInboundCancelled(previousState)) { + this.firstValue = null; + Operators.onDiscard(t, this.outboundSubscriber.currentContext()); + return true; + } + + final Publisher result; + final SwitchOnFirstControlSubscriber o = this.outboundSubscriber; + + try { + final Signal signal = Signal.next(t, o.currentContext()); + result = Objects.requireNonNull(this.transformer.apply(signal, this), + "The transformer returned a null value"); + } + catch (Throwable e) { + this.done = true; + + previousState = markInboundCancelledAndOutboundTerminated(this); + if (hasInboundCancelled(previousState) || hasOutboundCancelled(previousState)) { + Operators.onErrorDropped(e, o.currentContext()); + return true; + } + + this.firstValue = null; + Operators.onDiscard(t, o.currentContext()); + + o.errorDirectly(Operators.onOperatorError(this.s, e, t, o.currentContext())); + return true; + } + + result.subscribe(o); + return true; + } + + synchronized (this) { + return ((Fuseable.ConditionalSubscriber) this.inboundSubscriber).tryOnNext(t); + } + } + + @Override + @SuppressWarnings("unchecked") + boolean tryDirectSend(CoreSubscriber actual, T t) { + return ((Fuseable.ConditionalSubscriber) actual).tryOnNext(t); + } + } + + static class SwitchOnFirstControlSubscriber extends Operators.DeferredSubscription + implements InnerOperator, CoreSubscriber { + + final AbstractSwitchOnFirstMain parent; + final CoreSubscriber delegate; + final boolean cancelSourceOnComplete; + + boolean done; + + SwitchOnFirstControlSubscriber(AbstractSwitchOnFirstMain parent, + CoreSubscriber delegate, + boolean cancelSourceOnComplete) { + this.parent = parent; + this.delegate = delegate; + this.cancelSourceOnComplete = cancelSourceOnComplete; + } + + final void sendSubscription() { + delegate.onSubscribe(this); + } + + @Override + public final void onSubscribe(Subscription s) { + if (set(s)) { + long previousState = markOutboundSubscribed(this.parent); + + if (hasOutboundCancelled(previousState)) { + s.cancel(); + } + } + } + + @Override + public final CoreSubscriber actual() { + return this.delegate; + } + + @Override + public final void onNext(T t) { + if (this.done) { + Operators.onNextDropped(t, currentContext()); + return; + } + + this.delegate.onNext(t); + } + + @Override + public final void onError(Throwable throwable) { + if (this.done) { + Operators.onErrorDropped(throwable, currentContext()); + return; + } + + this.done = true; + + final AbstractSwitchOnFirstMain parent = this.parent; + long previousState = markOutboundTerminated(parent); + + if (hasOutboundCancelled(previousState) || hasOutboundTerminated(previousState)) { + Operators.onErrorDropped(throwable, this.delegate.currentContext()); + return; + } + + if (!hasInboundCancelled(previousState) && !hasInboundTerminated(previousState)) { + parent.cancelAndError(); + } + + this.delegate.onError(throwable); + } + + final void errorDirectly(Throwable t) { + this.done = true; + + this.delegate.onError(t); + } + + @Override + public final void onComplete() { + if (this.done) { + return; + } + + this.done = true; + + final AbstractSwitchOnFirstMain parent = this.parent; + long previousState = markOutboundTerminated(parent); + + if (cancelSourceOnComplete && !hasInboundCancelled(previousState) && !hasInboundTerminated(previousState)) { + parent.cancelAndError(); + } + + this.delegate.onComplete(); + } + + @Override + public final void cancel() { + Operators.DeferredSubscription.REQUESTED.lazySet(this, STATE_CANCELLED); + + final long previousState = markOutboundCancelled(this.parent); + if (hasOutboundCancelled(previousState) || hasOutboundTerminated(previousState)) { + return; + } + + final boolean shouldCancelInbound = + !hasInboundTerminated(previousState) && !hasInboundCancelled(previousState); + + if (!hasOutboundSubscribed(previousState)) { + if (shouldCancelInbound) { + this.parent.cancel(); + } + return; + } + + this.s.cancel(); + + if (shouldCancelInbound) { + this.parent.cancelAndError(); + } + } + + @Override + public final Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.ACTUAL) return delegate; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.CANCELLED) return hasOutboundCancelled(this.parent.state); + if (key == Attr.TERMINATED) return hasOutboundTerminated(this.parent.state); + + return null; + } + } + + static final class SwitchOnFirstConditionalControlSubscriber + extends SwitchOnFirstControlSubscriber + implements InnerOperator, Fuseable.ConditionalSubscriber { + + final Fuseable.ConditionalSubscriber delegate; + + SwitchOnFirstConditionalControlSubscriber(AbstractSwitchOnFirstMain parent, + Fuseable.ConditionalSubscriber delegate, + boolean cancelSourceOnComplete) { + super(parent, delegate, cancelSourceOnComplete); + this.delegate = delegate; + } + + @Override + public boolean tryOnNext(T t) { + if (this.done) { + Operators.onNextDropped(t, currentContext()); + return true; + } + + return this.delegate.tryOnNext(t); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTake.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTake.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTake.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.core.Fuseable.QueueSubscription; +import reactor.util.annotation.Nullable; + +/** + * Takes only the first N values from the source Publisher. + *

+ * If N is zero, the subscriber gets completed if the source completes, signals an error or + * signals its first value (which is not not relayed though). + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxTake extends InternalFluxOperator { + + final long n; + + FluxTake(Flux source, long n) { + super(source); + if (n < 0) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + this.n = n; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new TakeConditionalSubscriber<>((ConditionalSubscriber) actual, n); + } + else { + return new TakeSubscriber<>(actual, n); + } + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class TakeSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + + final long n; + + long remaining; + + Subscription s; + + boolean done; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(TakeSubscriber.class, "wip"); + + public TakeSubscriber(CoreSubscriber actual, long n) { + this.actual = actual; + this.n = n; + this.remaining = n; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + if (n == 0) { + s.cancel(); + done = true; + Operators.complete(actual); + } + else { + this.s = s; + actual.onSubscribe(this); + } + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + long r = remaining; + + if (r == 0) { + s.cancel(); + onComplete(); + return; + } + + remaining = --r; + boolean stop = r == 0L; + + actual.onNext(t); + + if (stop) { + s.cancel(); + + onComplete(); + } + } + + @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; + actual.onComplete(); + } + + @Override + public void request(long n) { + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + if (n >= this.n) { + s.request(Long.MAX_VALUE); + } else { + s.request(n); + } + return; + } + + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @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 InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + } + + static final class TakeConditionalSubscriber + implements ConditionalSubscriber, InnerOperator { + + final ConditionalSubscriber actual; + + final long n; + + long remaining; + + Subscription s; + + boolean done; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(TakeConditionalSubscriber.class, + "wip"); + + TakeConditionalSubscriber(ConditionalSubscriber actual, + long n) { + this.actual = actual; + this.n = n; + this.remaining = n; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + if (n == 0) { + s.cancel(); + done = true; + Operators.complete(actual); + } + else { + this.s = s; + actual.onSubscribe(this); + } + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + long r = remaining; + + if (r == 0) { + s.cancel(); + onComplete(); + return; + } + + remaining = --r; + boolean stop = r == 0L; + + actual.onNext(t); + + if (stop) { + s.cancel(); + + onComplete(); + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return true; + } + + long r = remaining; + + if (r == 0) { + s.cancel(); + onComplete(); + return true; + } + + remaining = --r; + boolean stop = r == 0L; + + boolean b = actual.tryOnNext(t); + + if (stop) { + s.cancel(); + + onComplete(); + } + return b; + } + + @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; + actual.onComplete(); + } + + @Override + public void request(long n) { + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + if (n >= this.n) { + s.request(Long.MAX_VALUE); + } + else { + s.request(n); + } + return; + } + + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @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 InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + } + + static final class TakeFuseableSubscriber + implements QueueSubscription, InnerOperator { + + final CoreSubscriber actual; + + final long n; + + long remaining; + + QueueSubscription qs; + + boolean done; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(TakeFuseableSubscriber.class, "wip"); + + int inputMode; + + TakeFuseableSubscriber(CoreSubscriber actual, long n) { + this.actual = actual; + this.n = n; + this.remaining = n; + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.qs, s)) { + if (n == 0) { + s.cancel(); + done = true; + Operators.complete(actual); + } + else { + this.qs = (QueueSubscription) s; + actual.onSubscribe(this); + } + } + } + + @Override + public void onNext(T t) { + + if (inputMode == Fuseable.ASYNC) { + actual.onNext(null); + return; + } + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + long r = remaining; + + if (r == 0) { + qs.cancel(); + onComplete(); + return; + } + + remaining = --r; + boolean stop = r == 0L; + + actual.onNext(t); + + if (stop) { + qs.cancel(); + + onComplete(); + } + } + + @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; + actual.onComplete(); + } + + @Override + public void request(long n) { + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + if (n >= this.n) { + qs.request(Long.MAX_VALUE); + } + else { + qs.request(n); + } + return; + } + + qs.request(n); + } + + @Override + public void cancel() { + qs.cancel(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + if (key == Attr.PARENT) return qs; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public int requestFusion(int requestedMode) { + int m = qs.requestFusion(requestedMode); + this.inputMode = m; + return m; + } + + @Override + @Nullable + public T poll() { + if (done) { + return null; + } + long r = remaining; + T v = qs.poll(); + if (r == 0L) { + done = true; + if (inputMode == Fuseable.ASYNC) { + qs.cancel(); + actual.onComplete(); + } + return null; + } + + + if (v != null) { + remaining = --r; + if (r == 0L) { + if (!done) { + done = true; + if (inputMode == Fuseable.ASYNC) { + qs.cancel(); + actual.onComplete(); + } + } + } + } + + return v; + } + + @Override + public boolean isEmpty() { + return remaining == 0 || qs.isEmpty(); + } + + @Override + public void clear() { + qs.clear(); + } + + @Override + public int size() { + return qs.size(); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016-2021 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.publisher.FluxTake.TakeFuseableSubscriber; + +/** + * Takes only the first N values from the source Publisher. + *

+ * If N is zero, the subscriber gets completed if the source completes, signals an error or + * signals its first value (which is not not relayed though). + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxTakeFuseable extends InternalFluxOperator implements Fuseable { + + final long n; + + FluxTakeFuseable(Flux source, long n) { + super(source); + if (n < 0) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + this.n = n; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new TakeFuseableSubscriber<>(actual, n); + } + + @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/FluxTakeLast.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeLast.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeLast.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2016-2021 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.ArrayDeque; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.BooleanSupplier; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Emits the last N values the source emitted before its completion. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class FluxTakeLast extends InternalFluxOperator { + + final int n; + + FluxTakeLast(Flux source, int n) { + super(source); + if (n < 0) { + throw new IllegalArgumentException("n >= required but it was " + n); + } + this.n = n; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (n == 0) { + return new TakeLastZeroSubscriber<>(actual); + } + else { + return new TakeLastManySubscriber<>(actual, n); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + static final class TakeLastZeroSubscriber implements InnerOperator { + + final CoreSubscriber actual; + + Subscription s; + + TakeLastZeroSubscriber(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; + + 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); + } + } + + @Override + public void onNext(T t) { + // ignoring all values + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } + + static final class TakeLastManySubscriber extends ArrayDeque + implements BooleanSupplier, InnerOperator { + + final CoreSubscriber actual; + + final int n; + + volatile boolean cancelled; + + Subscription s; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(TakeLastManySubscriber.class, + "requested"); + + TakeLastManySubscriber(CoreSubscriber actual, int n) { + this.actual = actual; + this.n = n; + } + + @Override + public boolean getAsBoolean() { + return cancelled; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + DrainUtils.postCompleteRequest(n, actual, this, REQUESTED, this, this); + } + } + + @Override + public void cancel() { + cancelled = true; + s.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 (size() == n) { + poll(); + } + offer(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + DrainUtils.postComplete(actual, this, REQUESTED, this, this); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.PARENT) return s; + if (key == Attr.BUFFERED) return size(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeLastOne.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeLastOne.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeLastOne.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Emits the last N values the source emitted before its completion. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxTakeLastOne extends InternalFluxOperator implements Fuseable { + + FluxTakeLastOne(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new MonoTakeLastOne.TakeLastOneSubscriber<>(actual, null, false); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @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/FluxTakeUntil.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntil.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntil.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Relays values until a predicate returns + * true, indicating the sequence should stop + * (checked after each value has been delivered). + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxTakeUntil extends InternalFluxOperator { + + final Predicate predicate; + + FluxTakeUntil(Flux source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new TakeUntilPredicateSubscriber<>(actual, predicate); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class TakeUntilPredicateSubscriber + implements InnerOperator { + final CoreSubscriber actual; + + final Predicate predicate; + + Subscription s; + + boolean done; + + TakeUntilPredicateSubscriber(CoreSubscriber actual, Predicate predicate) { + this.actual = actual; + this.predicate = predicate; + } + + @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; + } + + actual.onNext(t); + + boolean b; + + try { + b = predicate.test(t); + } catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + + return; + } + + if (b) { + s.cancel(); + + onComplete(); + } + } + + @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; + + actual.onComplete(); + } + + @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 InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntilOther.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntilOther.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeUntilOther.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2016-2021 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.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Relays values from the main Publisher until another Publisher signals an event. + * + * @param the value type of the main Publisher + * @param the value type of the other Publisher + * @see Reactive-Streams-Commons + */ +final class FluxTakeUntilOther extends InternalFluxOperator { + + final Publisher other; + + FluxTakeUntilOther(Flux source, Publisher other) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + TakeUntilMainSubscriber mainSubscriber = new TakeUntilMainSubscriber<>(actual); + + TakeUntilOtherSubscriber otherSubscriber = new TakeUntilOtherSubscriber<>(mainSubscriber); + + other.subscribe(otherSubscriber); + + return mainSubscriber; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class TakeUntilOtherSubscriber implements InnerConsumer { + final TakeUntilMainSubscriber main; + + boolean once; + + TakeUntilOtherSubscriber(TakeUntilMainSubscriber main) { + this.main = main; + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return main.other == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return main.other; + if (key == Attr.ACTUAL) return main; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + main.setOther(s); + + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(U t) { + onComplete(); + } + + @Override + public void onError(Throwable t) { + if (once) { + return; + } + once = true; + main.onError(t); + } + + @Override + public void onComplete() { + if (once) { + return; + } + once = true; + main.onComplete(); + } + } + + static final class TakeUntilMainSubscriber implements InnerOperator { + + final CoreSubscriber actual; + + volatile Subscription main; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater MAIN = + AtomicReferenceFieldUpdater.newUpdater(TakeUntilMainSubscriber.class, Subscription.class, "main"); + + volatile Subscription other; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater OTHER = + AtomicReferenceFieldUpdater.newUpdater(TakeUntilMainSubscriber.class, Subscription.class, "other"); + + TakeUntilMainSubscriber(CoreSubscriber actual) { + this.actual = Operators.serialize(actual); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return main; + if (key == Attr.CANCELLED) return main == Operators.cancelledSubscription(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(other)); + } + + void setOther(Subscription s) { + if (!OTHER.compareAndSet(this, null, s)) { + s.cancel(); + if (other != Operators.cancelledSubscription()) { + Operators.reportSubscriptionSet(); + } + } + } + + @Override + public void request(long n) { + main.request(n); + } + + void cancelMain() { + Subscription s = main; + if (s != Operators.cancelledSubscription()) { + s = MAIN.getAndSet(this, Operators.cancelledSubscription()); + if (s != null && s != Operators.cancelledSubscription()) { + s.cancel(); + } + } + } + + void cancelOther() { + Subscription s = other; + if (s != Operators.cancelledSubscription()) { + s = OTHER.getAndSet(this, Operators.cancelledSubscription()); + if (s != null && s != Operators.cancelledSubscription()) { + s.cancel(); + } + } + } + + @Override + public void cancel() { + cancelMain(); + cancelOther(); + } + + @Override + public void onSubscribe(Subscription s) { + if (!MAIN.compareAndSet(this, null, s)) { + s.cancel(); + if (main != Operators.cancelledSubscription()) { + Operators.reportSubscriptionSet(); + } + } else { + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + + if (main == null) { + if (MAIN.compareAndSet(this, null, Operators.cancelledSubscription())) { + Operators.error(actual, t); + return; + } + } + cancel(); + + actual.onError(t); + } + + @Override + public void onComplete() { + if (main == null) { + if (MAIN.compareAndSet(this, null, Operators.cancelledSubscription())) { + cancelOther(); + Operators.complete(actual); + return; + } + } + cancel(); + + actual.onComplete(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeWhile.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeWhile.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTakeWhile.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Relays values while a predicate returns + * true for the values (checked before each value is delivered). + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class FluxTakeWhile extends InternalFluxOperator { + + final Predicate predicate; + + FluxTakeWhile(Flux source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new TakeWhileSubscriber<>(actual, predicate); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class TakeWhileSubscriber + implements InnerOperator { + final CoreSubscriber actual; + + final Predicate predicate; + + Subscription s; + + boolean done; + + TakeWhileSubscriber(CoreSubscriber actual, Predicate predicate) { + this.actual = actual; + this.predicate = predicate; + } + + @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; + } + + boolean b; + + try { + b = predicate.test(t); + } catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + + return; + } + + if (!b) { + s.cancel(); + + onComplete(); + + return; + } + + actual.onNext(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; + + actual.onComplete(); + } + + @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 InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTimed.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTimed.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTimed.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2020-2021 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.time.Instant; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +/** + * @author Simon Baslé + */ +final class FluxTimed extends InternalFluxOperator> { + + final Scheduler clock; + + FluxTimed(Flux source, Scheduler clock) { + super(source); + this.clock = clock; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new TimedSubscriber<>(actual, this.clock); + } + + @Override + public int getPrefetch() { + return 0; + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + /** + * Immutable version of {@link Timed}. This is preferable to the subscriber implementing + * Timed interface, as timestamps are likely to be collected for later use (so flyweight + * would get in the way). + * + * @param + */ + static final class ImmutableTimed implements Timed { + + final long eventElapsedSinceSubscriptionNanos; + final long eventElapsedNanos; + final long eventTimestampEpochMillis; + final T event; + + ImmutableTimed(long eventElapsedSinceSubscriptionNanos, + long eventElapsedNanos, + long eventTimestampEpochMillis, + T event) { + this.eventElapsedSinceSubscriptionNanos = eventElapsedSinceSubscriptionNanos; + this.eventElapsedNanos = eventElapsedNanos; + this.eventTimestampEpochMillis = eventTimestampEpochMillis; + this.event = event; + } + + @Override + public T get() { + return this.event; + } + + @Override + public Duration elapsed() { + return Duration.ofNanos(eventElapsedNanos); + } + + @Override + public Duration elapsedSinceSubscription() { + return Duration.ofNanos(eventElapsedSinceSubscriptionNanos); + } + + @Override + public Instant timestamp() { + return Instant.ofEpochMilli(eventTimestampEpochMillis); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ImmutableTimed timed = (ImmutableTimed) o; + return eventElapsedSinceSubscriptionNanos == timed.eventElapsedSinceSubscriptionNanos && eventElapsedNanos == timed.eventElapsedNanos && eventTimestampEpochMillis == timed.eventTimestampEpochMillis && event.equals( + timed.event); + } + + @Override + public int hashCode() { + return Objects.hash(eventElapsedSinceSubscriptionNanos, + eventElapsedNanos, + eventTimestampEpochMillis, + event); + } + + @Override + public String toString() { + return "Timed(" + event + "){eventElapsedNanos=" + eventElapsedNanos + ", eventElapsedSinceSubscriptionNanos=" + eventElapsedSinceSubscriptionNanos + ", eventTimestampEpochMillis=" + eventTimestampEpochMillis + '}'; + } + } + + static final class TimedSubscriber implements InnerOperator> { + + final CoreSubscriber> actual; + final Scheduler clock; + + long subscriptionNanos; + long lastEventNanos; + + boolean done; + Subscription s; + + TimedSubscriber(CoreSubscriber> actual, Scheduler clock) { + this.actual = actual; + this.clock = clock; + } + + @Override + public CoreSubscriber> actual() { + return this.actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + this.subscriptionNanos = clock.now(TimeUnit.NANOSECONDS); + this.lastEventNanos = subscriptionNanos; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, currentContext()); + return; + } + + long nowNanos = clock.now(TimeUnit.NANOSECONDS); + long timestamp = clock.now(TimeUnit.MILLISECONDS); + Timed timed = new ImmutableTimed<>(nowNanos - this.subscriptionNanos, nowNanos - this.lastEventNanos, timestamp, t); + this.lastEventNanos = nowNanos; + actual.onNext(timed); + } + + @Override + public void onError(Throwable throwable) { + if (done) { + Operators.onErrorDropped(throwable, currentContext()); + return; + } + done = true; + actual.onError(throwable); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + actual.onComplete(); + } + + @Override + public void request(long l) { + if (Operators.validate(l)) { + s.request(l); + } + } + + @Override + public void cancel() { + s.cancel(); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxTimeout.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxTimeout.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxTimeout.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2016-2021 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.TimeoutException; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Signals a timeout (or switches to another sequence) in case a per-item + * generated Publisher source fires an item or completes before the next item + * arrives from the main source. + * + * @param the main source type + * @param the value type for the timeout for the very first item + * @param the value type for the timeout for the subsequent items + * + * @see Reactive-Streams-Commons + */ +final class FluxTimeout extends InternalFluxOperator { + + final Publisher firstTimeout; + + final Function> itemTimeout; + + final Publisher other; + + final String timeoutDescription; //only useful when no `other` + + FluxTimeout(Flux source, + Publisher firstTimeout, + Function> itemTimeout, + String timeoutDescription) { + super(source); + this.firstTimeout = Objects.requireNonNull(firstTimeout, "firstTimeout"); + this.itemTimeout = Objects.requireNonNull(itemTimeout, "itemTimeout"); + this.other = null; + + this.timeoutDescription = addNameToTimeoutDescription(source, + Objects.requireNonNull(timeoutDescription, "timeoutDescription is needed when no fallback")); + } + + FluxTimeout(Flux source, + Publisher firstTimeout, + Function> itemTimeout, + Publisher other) { + super(source); + this.firstTimeout = Objects.requireNonNull(firstTimeout, "firstTimeout"); + this.itemTimeout = Objects.requireNonNull(itemTimeout, "itemTimeout"); + this.other = Objects.requireNonNull(other, "other"); + this.timeoutDescription = null; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new TimeoutMainSubscriber<>(actual, firstTimeout, itemTimeout, other, timeoutDescription); + } + + @Nullable + static String addNameToTimeoutDescription(Publisher source, + @Nullable String timeoutDescription) { + if (timeoutDescription == null) { + return null; + } + + Scannable s = Scannable.from(source); + if (s.isScanAvailable()) { + return timeoutDescription + " in '" + s.name() + "'"; + } + else { + return timeoutDescription; + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class TimeoutMainSubscriber + extends Operators.MultiSubscriptionSubscriber { + + final Publisher firstTimeout; + + final Function> itemTimeout; + + final Publisher other; + final String timeoutDescription; //only useful/non-null when no `other` + + Subscription s; + + volatile IndexedCancellable timeout; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + TIMEOUT = + AtomicReferenceFieldUpdater.newUpdater(TimeoutMainSubscriber.class, + IndexedCancellable.class, + "timeout"); + + volatile long index; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater INDEX = + AtomicLongFieldUpdater.newUpdater(TimeoutMainSubscriber.class, "index"); + + TimeoutMainSubscriber( + CoreSubscriber actual, + Publisher firstTimeout, + Function> itemTimeout, + @Nullable Publisher other, + @Nullable String timeoutDescription + ) { + super(Operators.serialize(actual)); + this.itemTimeout = itemTimeout; + this.other = other; + this.timeoutDescription = timeoutDescription; + this.firstTimeout = firstTimeout; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + set(s); + + TimeoutTimeoutSubscriber timeoutSubscriber = new TimeoutTimeoutSubscriber(this, 0L); + this.timeout = timeoutSubscriber; + + actual.onSubscribe(this); + firstTimeout.subscribe(timeoutSubscriber); + } + } + + @Override + protected boolean shouldCancelCurrent() { + return true; + } + + @Override + public void onNext(T t) { + timeout.cancel(); + + long idx = index; + if (idx == Long.MIN_VALUE) { + s.cancel(); + Operators.onNextDropped(t, actual.currentContext()); + return; + } + if (!INDEX.compareAndSet(this, idx, idx + 1)) { + s.cancel(); + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + actual.onNext(t); + + producedOne(); + + Publisher p; + + try { + p = Objects.requireNonNull(itemTimeout.apply(t), + "The itemTimeout returned a null Publisher"); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(this, e, t, + actual.currentContext())); + return; + } + + TimeoutTimeoutSubscriber ts = new TimeoutTimeoutSubscriber(this, idx + 1); + + if (!setTimeout(ts)) { + return; + } + + p.subscribe(ts); + } + + @Override + public void onError(Throwable t) { + long idx = index; + if (idx == Long.MIN_VALUE) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + if (!INDEX.compareAndSet(this, idx, Long.MIN_VALUE)) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + + cancelTimeout(); + + actual.onError(t); + } + + @Override + public void onComplete() { + long idx = index; + if (idx == Long.MIN_VALUE) { + return; + } + if (!INDEX.compareAndSet(this, idx, Long.MIN_VALUE)) { + return; + } + + cancelTimeout(); + + actual.onComplete(); + } + + void cancelTimeout() { + IndexedCancellable s = timeout; + if (s != CancelledIndexedCancellable.INSTANCE) { + s = TIMEOUT.getAndSet(this, CancelledIndexedCancellable.INSTANCE); + if (s != null && s != CancelledIndexedCancellable.INSTANCE) { + s.cancel(); + } + } + } + + @Override + public void cancel() { + index = Long.MIN_VALUE; + cancelTimeout(); + super.cancel(); + } + + boolean setTimeout(IndexedCancellable newTimeout) { + + for (; ; ) { + IndexedCancellable currentTimeout = timeout; + + if (currentTimeout == CancelledIndexedCancellable.INSTANCE) { + newTimeout.cancel(); + return false; + } + + if (currentTimeout != null && currentTimeout.index() >= newTimeout.index()) { + newTimeout.cancel(); + return false; + } + + if (TIMEOUT.compareAndSet(this, currentTimeout, newTimeout)) { + if (currentTimeout != null) { + currentTimeout.cancel(); + } + return true; + } + } + } + + void doTimeout(long i) { + if (index == i && INDEX.compareAndSet(this, i, Long.MIN_VALUE)) { + handleTimeout(); + } + } + + void doError(long i, Throwable e) { + if (index == i && INDEX.compareAndSet(this, i, Long.MIN_VALUE)) { + super.cancel(); + + actual.onError(e); + } + } + + void handleTimeout() { + if (other == null) { + super.cancel(); + actual.onError(new TimeoutException("Did not observe any item or terminal signal within " + + timeoutDescription + " (and no fallback has been configured)")); + } + else { + set(Operators.emptySubscription()); + + other.subscribe(new TimeoutOtherSubscriber<>(actual, this)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + } + + static final class TimeoutOtherSubscriber implements InnerConsumer { + + final CoreSubscriber actual; + + final Operators.MultiSubscriptionSubscriber arbiter; + + TimeoutOtherSubscriber(CoreSubscriber actual, + Operators.MultiSubscriptionSubscriber arbiter) { + this.actual = actual; + this.arbiter = arbiter; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + arbiter.set(s); + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + } + + interface IndexedCancellable { + + long index(); + + void cancel(); + } + + enum CancelledIndexedCancellable implements IndexedCancellable { + INSTANCE; + + @Override + public long index() { + return Long.MAX_VALUE; + } + + @Override + public void cancel() { + + } + + } + + static final class TimeoutTimeoutSubscriber + implements InnerConsumer, IndexedCancellable { + + final TimeoutMainSubscriber main; + + final long index; + + volatile Subscription s; + + static final AtomicReferenceFieldUpdater + S = AtomicReferenceFieldUpdater.newUpdater(TimeoutTimeoutSubscriber.class, + Subscription.class, + "s"); + + TimeoutTimeoutSubscriber(TimeoutMainSubscriber main, long index) { + this.main = main; + this.index = index; + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + if (!S.compareAndSet(this, null, s)) { + s.cancel(); + if (this.s != Operators.cancelledSubscription()) { + Operators.reportSubscriptionSet(); + } + } + else { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Object t) { + s.cancel(); + + main.doTimeout(index); + } + + @Override + public void onError(Throwable t) { + main.doError(index, t); + } + + @Override + public void onComplete() { + main.doTimeout(index); + } + + @Override + public void cancel() { + Subscription a = s; + if (a != Operators.cancelledSubscription()) { + a = S.getAndSet(this, Operators.cancelledSubscription()); + if (a != null && a != Operators.cancelledSubscription()) { + a.cancel(); + } + } + } + + @Override + public long index() { + return index; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxUsing.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxUsing.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxUsing.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,583 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Uses a resource, generated by a supplier for each individual Subscriber, + * while streaming the values from a + * Publisher derived from the same resource and makes sure the resource is released + * if the sequence terminates or the Subscriber cancels. + *

+ *

+ * Eager resource cleanup happens just before the source termination and exceptions + * raised by the cleanup Consumer may override the terminal event. Non-eager + * cleanup will drop any exception. + * + * @param the value type streamed + * @param the resource type + * + * @see Reactive-Streams-Commons + */ +final class FluxUsing extends Flux implements Fuseable, SourceProducer { + + final Callable resourceSupplier; + + final Function> sourceFactory; + + final Consumer resourceCleanup; + + final boolean eager; + + FluxUsing(Callable resourceSupplier, + Function> sourceFactory, + Consumer resourceCleanup, + boolean eager) { + this.resourceSupplier = + Objects.requireNonNull(resourceSupplier, "resourceSupplier"); + this.sourceFactory = Objects.requireNonNull(sourceFactory, "sourceFactory"); + this.resourceCleanup = Objects.requireNonNull(resourceCleanup, "resourceCleanup"); + this.eager = eager; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + S resource; + + try { + resource = resourceSupplier.call(); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + + Publisher p; + + try { + p = Objects.requireNonNull(sourceFactory.apply(resource), + "The sourceFactory returned a null value"); + } + catch (Throwable e) { + Throwable _e = Operators.onOperatorError(e, actual.currentContext()); + try { + resourceCleanup.accept(resource); + } + catch (Throwable ex) { + _e = Exceptions.addSuppressed(ex, _e); + } + + Operators.error(actual, _e); + return; + } + + if (p instanceof Fuseable) { + from(p).subscribe(new UsingFuseableSubscriber<>(actual, + resourceCleanup, + resource, + eager)); + } + else if (actual instanceof ConditionalSubscriber) { + from(p).subscribe(new UsingConditionalSubscriber<>((ConditionalSubscriber) actual, + resourceCleanup, + resource, + eager)); + } + else { + from(p).subscribe(new UsingSubscriber<>(actual, resourceCleanup, + resource, eager)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class UsingSubscriber + implements InnerOperator, QueueSubscription { + + final CoreSubscriber actual; + + final Consumer resourceCleanup; + + final S resource; + + final boolean eager; + + Subscription s; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(UsingSubscriber.class, "wip"); + + UsingSubscriber(CoreSubscriber actual, + Consumer resourceCleanup, + S resource, + boolean eager) { + this.actual = actual; + this.resourceCleanup = resourceCleanup; + this.resource = resource; + this.eager = eager; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return wip == 1; + if (key == Attr.PARENT) return s; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + if (WIP.compareAndSet(this, 0, 1)) { + s.cancel(); + + cleanup(); + } + } + + void cleanup() { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Throwable _e = Operators.onOperatorError(e, actual.currentContext()); + t = Exceptions.addSuppressed(_e, t); + } + } + + actual.onError(t); + + if (!eager && WIP.compareAndSet(this, 0, 1)) { + cleanup(); + } + } + + @Override + public void onComplete() { + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + return; + } + } + + actual.onComplete(); + + if (!eager && WIP.compareAndSet(this, 0, 1)) { + cleanup(); + } + } + + @Override + public int requestFusion(int requestedMode) { + return NONE; // always reject, upstream turned out to be non-fuseable after all + } + + @Override + public void clear() { + // ignoring fusion methods + } + + @Override + public boolean isEmpty() { + // ignoring fusion methods + return true; + } + + @Override + @Nullable + public T poll() { + return null; + } + + @Override + public int size() { + return 0; + } + } + + static final class UsingFuseableSubscriber + implements InnerOperator, QueueSubscription { + + final CoreSubscriber actual; + + final Consumer resourceCleanup; + + final S resource; + + final boolean eager; + + QueueSubscription s; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(UsingFuseableSubscriber.class, + "wip"); + + int mode; + + UsingFuseableSubscriber(CoreSubscriber actual, + Consumer resourceCleanup, + S resource, + boolean eager) { + this.actual = actual; + this.resourceCleanup = resourceCleanup; + this.resource = resource; + this.eager = eager; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) + return wip == 1; + if (key == Attr.PARENT) return s; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + if (WIP.compareAndSet(this, 0, 1)) { + s.cancel(); + + cleanup(); + } + } + + void cleanup() { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = (QueueSubscription) s; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Throwable _e = Operators.onOperatorError(e, actual.currentContext()); + t = Exceptions.addSuppressed(_e, t); + } + } + + actual.onError(t); + + if (!eager && WIP.compareAndSet(this, 0, 1)) { + cleanup(); + } + } + + @Override + public void onComplete() { + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + return; + } + } + + actual.onComplete(); + + if (!eager && WIP.compareAndSet(this, 0, 1)) { + cleanup(); + } + } + + @Override + public void clear() { + s.clear(); + } + + @Override + public boolean isEmpty() { + return s.isEmpty(); + } + + @Override + @Nullable + public T poll() { + T v = s.poll(); + + if (v == null && mode == SYNC) { + if (WIP.compareAndSet(this, 0, 1)) { + resourceCleanup.accept(resource); + } + } + return v; + } + + @Override + public int requestFusion(int requestedMode) { + int m = s.requestFusion(requestedMode); + mode = m; + return m; + } + + @Override + public int size() { + return s.size(); + } + } + + static final class UsingConditionalSubscriber + implements ConditionalSubscriber, InnerOperator, + QueueSubscription { + + final ConditionalSubscriber actual; + + final Consumer resourceCleanup; + + final S resource; + + final boolean eager; + + Subscription s; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(UsingConditionalSubscriber.class, + "wip"); + + UsingConditionalSubscriber(ConditionalSubscriber actual, + Consumer resourceCleanup, + S resource, + boolean eager) { + this.actual = actual; + this.resourceCleanup = resourceCleanup; + this.resource = resource; + this.eager = eager; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) + return wip == 1; + if (key == Attr.PARENT) return s; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + if (WIP.compareAndSet(this, 0, 1)) { + s.cancel(); + + cleanup(); + } + } + + void cleanup() { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public boolean tryOnNext(T t) { + return actual.tryOnNext(t); + } + + @Override + public void onError(Throwable t) { + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Throwable _e = Operators.onOperatorError(e, actual.currentContext()); + t = Exceptions.addSuppressed(_e, t); + } + } + + actual.onError(t); + + if (!eager && WIP.compareAndSet(this, 0, 1)) { + cleanup(); + } + } + + @Override + public void onComplete() { + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + return; + } + } + + actual.onComplete(); + + if (!eager && WIP.compareAndSet(this, 0, 1)) { + cleanup(); + } + } + + @Override + public int requestFusion(int requestedMode) { + return NONE; // always reject, upstream turned out to be non-fuseable after all + } + + @Override + public void clear() { + // ignoring fusion methods + } + + @Override + public boolean isEmpty() { + // ignoring fusion methods + return true; + } + + @Override + @Nullable + public T poll() { + return null; + } + + @Override + public int size() { + return 0; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxUsingWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxUsingWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxUsingWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2018-2021 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.Callable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiFunction; +import java.util.function.Function; + +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.Operators.DeferredSubscription; +import reactor.util.Loggers; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Uses a resource that is lazily generated by a {@link Publisher} for each individual{@link Subscriber}, + * while streaming the values from a {@link Publisher} derived from the same resource. + * Whenever the resulting sequence terminates, the relevant {@link Function} generates + * a "cleanup" {@link Publisher} that is invoked but doesn't change the content of the + * main sequence. Instead it just defers the termination (unless it errors, in which case + * the error suppresses the original termination signal). + *

+ * Note that if the resource supplying {@link Publisher} emits more than one resource, the + * subsequent resources are dropped ({@link Operators#onNextDropped(Object, Context)}). If + * the publisher errors AFTER having emitted one resource, the error is also silently dropped + * ({@link Operators#onErrorDropped(Throwable, Context)}). + * + * @param the value type streamed + * @param the resource type + */ +final class FluxUsingWhen extends Flux implements SourceProducer { + + final Publisher resourceSupplier; + final Function> resourceClosure; + final Function> asyncComplete; + final BiFunction> asyncError; + @Nullable + final Function> asyncCancel; + + FluxUsingWhen(Publisher resourceSupplier, + Function> resourceClosure, + Function> asyncComplete, + BiFunction> asyncError, + @Nullable Function> asyncCancel) { + this.resourceSupplier = Objects.requireNonNull(resourceSupplier, "resourceSupplier"); + this.resourceClosure = Objects.requireNonNull(resourceClosure, "resourceClosure"); + this.asyncComplete = Objects.requireNonNull(asyncComplete, "asyncComplete"); + this.asyncError = Objects.requireNonNull(asyncError, "asyncError"); + this.asyncCancel = asyncCancel; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + if (resourceSupplier instanceof Callable) { + try { + Callable resourceCallable = (Callable) resourceSupplier; + S resource = resourceCallable.call(); + if (resource == null) { + Operators.complete(actual); + } + else { + Publisher p = deriveFluxFromResource(resource, resourceClosure); + UsingWhenSubscriber subscriber = prepareSubscriberForResource(resource, + actual, + asyncComplete, + asyncError, + asyncCancel, + null); + + p.subscribe(subscriber); + } + } + catch (Throwable e) { + Operators.error(actual, e); + } + return; + } + + //trigger the resource creation and delay the subscription to actual + resourceSupplier.subscribe(new ResourceSubscriber(actual, resourceClosure, asyncComplete, asyncError, asyncCancel, resourceSupplier instanceof Mono)); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + private static Publisher deriveFluxFromResource( + RESOURCE resource, + Function> resourceClosure) { + + Publisher p; + + try { + p = Objects.requireNonNull(resourceClosure.apply(resource), + "The resourceClosure function returned a null value"); + } + catch (Throwable e) { + p = Flux.error(e); + } + + return p; + } + + private static UsingWhenSubscriber prepareSubscriberForResource( + RESOURCE resource, + CoreSubscriber actual, + Function> asyncComplete, + BiFunction> asyncError, + @Nullable Function> asyncCancel, + @Nullable DeferredSubscription arbiter) { + if (actual instanceof ConditionalSubscriber) { + @SuppressWarnings("unchecked") + ConditionalSubscriber conditionalActual = (ConditionalSubscriber) actual; + return new UsingWhenConditionalSubscriber<>(conditionalActual, + resource, + asyncComplete, + asyncError, + asyncCancel, + arbiter); + } + else { + return new UsingWhenSubscriber<>(actual, + resource, + asyncComplete, + asyncError, + asyncCancel, + arbiter); + } + } + + static class ResourceSubscriber extends DeferredSubscription + implements InnerConsumer { + + final CoreSubscriber actual; + final Function> resourceClosure; + final Function> asyncComplete; + final BiFunction> asyncError; + @Nullable + final Function> asyncCancel; + final boolean isMonoSource; + + Subscription resourceSubscription; + boolean resourceProvided; + + UsingWhenSubscriber closureSubscriber; + + ResourceSubscriber(CoreSubscriber actual, + Function> resourceClosure, + Function> asyncComplete, + BiFunction> asyncError, + @Nullable Function> asyncCancel, + boolean isMonoSource) { + this.actual = Objects.requireNonNull(actual, "actual"); + this.resourceClosure = Objects.requireNonNull(resourceClosure, "resourceClosure"); + this.asyncComplete = Objects.requireNonNull(asyncComplete, "asyncComplete"); + this.asyncError = Objects.requireNonNull(asyncError, "asyncError"); + this.asyncCancel = asyncCancel; + this.isMonoSource = isMonoSource; + } + + @Override + public void onNext(S resource) { + if (resourceProvided) { + Operators.onNextDropped(resource, actual.currentContext()); + return; + } + resourceProvided = true; + + final Publisher p = deriveFluxFromResource(resource, resourceClosure); + this.closureSubscriber = prepareSubscriberForResource(resource, + this.actual, + this.asyncComplete, + this.asyncError, + this.asyncCancel, + this); + + p.subscribe(closureSubscriber); + + if (!isMonoSource) { + resourceSubscription.cancel(); + } + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @Override + public void onError(Throwable throwable) { + if (resourceProvided) { + Operators.onErrorDropped(throwable, actual.currentContext()); + return; + } + //even if no resource provided, actual.onSubscribe has been called + //let's immediately complete actual + actual.onError(throwable); + } + + @Override + public void onComplete() { + if (resourceProvided) { + return; + } + //even if no resource provided, actual.onSubscribe has been called + //let's immediately complete actual + actual.onComplete(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.resourceSubscription, s)) { + this.resourceSubscription = s; + actual.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void cancel() { + if (!resourceProvided) { + resourceSubscription.cancel(); + super.cancel(); + } + else { + super.terminate(); + if (closureSubscriber != null) { + closureSubscriber.cancel(); + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return resourceSubscription; + if (key == Attr.ACTUAL) return actual; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.TERMINATED) return resourceProvided; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + } + + static class UsingWhenSubscriber implements UsingWhenParent { + + //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; + final BiFunction> asyncError; + @Nullable + final Function> asyncCancel; + @Nullable + final DeferredSubscription arbiter; + + volatile int callbackApplied; + static final AtomicIntegerFieldUpdater CALLBACK_APPLIED = AtomicIntegerFieldUpdater.newUpdater(UsingWhenSubscriber.class, "callbackApplied"); + + /** + * Also stores the onComplete terminal state as {@link Exceptions#TERMINATED} + */ + Throwable error; + + UsingWhenSubscriber(CoreSubscriber actual, + S resource, + Function> asyncComplete, + BiFunction> asyncError, + @Nullable Function> asyncCancel, + @Nullable DeferredSubscription arbiter) { + this.actual = actual; + this.resource = resource; + this.asyncComplete = asyncComplete; + this.asyncError = asyncError; + this.asyncCancel = asyncCancel; + this.arbiter = arbiter; + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Nullable + 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.PARENT) return s; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return UsingWhenParent.super.scanUnsafe(key); + } + + @Override + public void request(long l) { + if (Operators.validate(l)) { + s.request(l); + } + } + + @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)); + } + } + catch (Throwable error) { + Loggers.getLogger(FluxUsingWhen.class).warn("Error generating async resource cleanup during onCancel", error); + } + } + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + if (CALLBACK_APPLIED.compareAndSet(this, 0, 1)) { + Publisher p; + + try { + p = Objects.requireNonNull(asyncError.apply(resource, t), + "The asyncError returned a null Publisher"); + } + catch (Throwable e) { + Throwable _e = Operators.onOperatorError(e, actual.currentContext()); + _e = Exceptions.addSuppressed(_e, t); + actual.onError(_e); + return; + } + + p.subscribe(new RollbackInner(this, t)); + } + } + + @Override + public void onComplete() { + if (CALLBACK_APPLIED.compareAndSet(this, 0, 1)) { + Publisher p; + + try { + p = Objects.requireNonNull(asyncComplete.apply(resource), + "The asyncComplete returned a null Publisher"); + } + catch (Throwable e) { + Throwable _e = Operators.onOperatorError(e, actual.currentContext()); + //give a chance for the Mono implementation to discard the recorded value + deferredError(_e); + return; + } + + p.subscribe(new CommitInner(this)); + } + } + + + @Override + public void deferredComplete() { + this.error = Exceptions.TERMINATED; + this.actual.onComplete(); + } + + @Override + public void deferredError(Throwable error) { + this.error = error; + this.actual.onError(error); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + if (arbiter == null) { + actual.onSubscribe(this); + } + else { + arbiter.set(s); + } + } + } + } + + static final class UsingWhenConditionalSubscriber + extends UsingWhenSubscriber + implements ConditionalSubscriber { + + final ConditionalSubscriber actual; + + UsingWhenConditionalSubscriber(ConditionalSubscriber actual, + S resource, + Function> asyncComplete, + BiFunction> asyncError, + @Nullable Function> asyncCancel, + @Nullable DeferredSubscription arbiter) { + super(actual, resource, asyncComplete, asyncError, asyncCancel, arbiter); + this.actual = actual; + } + + @Override + public boolean tryOnNext(T t) { + return actual.tryOnNext(t); + } + } + + static final class RollbackInner implements InnerConsumer { + + final UsingWhenParent parent; + final Throwable rollbackCause; + + boolean done; + + RollbackInner(UsingWhenParent ts, Throwable rollbackCause) { + this.parent = ts; + this.rollbackCause = rollbackCause; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + Objects.requireNonNull(s, "Subscription cannot be null") + .request(Long.MAX_VALUE); + } + + @Override + public void onNext(Object o) { + //NO-OP + } + + @Override + public void onError(Throwable e) { + done = true; + RuntimeException rollbackError = new RuntimeException("Async resource cleanup failed after onError", e); + parent.deferredError(Exceptions.addSuppressed(rollbackError, rollbackCause)); + } + + @Override + public void onComplete() { + done = true; + parent.deferredError(rollbackCause); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.ACTUAL) return parent.actual(); + if (key == Attr.ERROR) return rollbackCause; + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } + + static final class CommitInner implements InnerConsumer { + + final UsingWhenParent parent; + + boolean done; + + CommitInner(UsingWhenParent ts) { + this.parent = ts; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + Objects.requireNonNull(s, "Subscription cannot be null") + .request(Long.MAX_VALUE); + } + + @Override + public void onNext(Object o) { + //NO-OP + } + + @Override + public void onError(Throwable e) { + done = true; + Throwable e_ = Operators.onOperatorError(e, parent.currentContext()); + Throwable commitError = new RuntimeException("Async resource cleanup failed after onComplete", e_); + parent.deferredError(commitError); + } + + @Override + public void onComplete() { + done = true; + parent.deferredComplete(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.ACTUAL) return parent.actual(); + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } + + /** + * Used in the cancel path to still give the generated Publisher access to the Context + */ + static final class CancelInner implements InnerConsumer { + + final UsingWhenParent parent; + + CancelInner(UsingWhenParent ts) { + this.parent = ts; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + Objects.requireNonNull(s, "Subscription cannot be null") + .request(Long.MAX_VALUE); + } + + @Override + public void onNext(Object o) { + //NO-OP + } + + @Override + public void onError(Throwable e) { + Loggers.getLogger(FluxUsingWhen.class).warn("Async resource cleanup failed after cancel", e); + } + + @Override + public void onComplete() { + //NO-OP + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.ACTUAL) return parent.actual(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } + + private interface UsingWhenParent extends InnerOperator { + + void deferredComplete(); + + void deferredError(Throwable error); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindow.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindow.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindow.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2016-2021 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.ArrayDeque; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Splits the source sequence into possibly overlapping publishers. + * + * @param the value type + * + * @see https://github.com/reactor/reactive-streams-commons + */ +final class FluxWindow extends InternalFluxOperator> { + + final int size; + + final int skip; + + final Supplier> processorQueueSupplier; + + final Supplier>> overflowQueueSupplier; + + FluxWindow(Flux source, + int size, + Supplier> processorQueueSupplier) { + super(source); + if (size <= 0) { + throw new IllegalArgumentException("size > 0 required but it was " + size); + } + this.size = size; + this.skip = size; + this.processorQueueSupplier = + Objects.requireNonNull(processorQueueSupplier, "processorQueueSupplier"); + this.overflowQueueSupplier = null; // won't be needed here + } + + FluxWindow(Flux source, + int size, + int skip, + Supplier> processorQueueSupplier, + Supplier>> overflowQueueSupplier) { + super(source); + if (size <= 0) { + throw new IllegalArgumentException("size > 0 required but it was " + size); + } + if (skip <= 0) { + throw new IllegalArgumentException("skip > 0 required but it was " + skip); + } + this.size = size; + this.skip = skip; + this.processorQueueSupplier = + Objects.requireNonNull(processorQueueSupplier, "processorQueueSupplier"); + this.overflowQueueSupplier = + Objects.requireNonNull(overflowQueueSupplier, "overflowQueueSupplier"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + if (skip == size) { + return new WindowExactSubscriber<>(actual, + size, + processorQueueSupplier); + } + else if (skip > size) { + return new WindowSkipSubscriber<>(actual, + size, skip, processorQueueSupplier); + } + else { + return new WindowOverlapSubscriber<>(actual, + size, + skip, processorQueueSupplier, overflowQueueSupplier.get()); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class WindowExactSubscriber + implements Disposable, InnerOperator> { + + final CoreSubscriber> actual; + + final Supplier> processorQueueSupplier; + + final int size; + + volatile int cancelled; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater CANCELLED = + AtomicIntegerFieldUpdater.newUpdater(WindowExactSubscriber.class, "cancelled"); + + volatile int windowCount; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WINDOW_COUNT = + AtomicIntegerFieldUpdater.newUpdater(WindowExactSubscriber.class, "windowCount"); + + int index; + + Subscription s; + + Sinks.Many window; + + boolean done; + + WindowExactSubscriber(CoreSubscriber> actual, + int size, + Supplier> processorQueueSupplier) { + this.actual = actual; + this.size = size; + this.processorQueueSupplier = processorQueueSupplier; + WINDOW_COUNT.lazySet(this, 1); + } + + @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; + } + + int i = index; + + Sinks.Many w = window; + if (cancelled == 0 && i == 0) { + WINDOW_COUNT.getAndIncrement(this); + + w = Sinks.unsafe().many().unicast().onBackpressureBuffer(processorQueueSupplier.get(), this); + window = w; + + actual.onNext(w.asFlux()); + } + + i++; + + w.emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST); + + if (i == size) { + index = 0; + window = null; + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + else { + index = i; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + Sinks.Many w = window; + if (w != null) { + window = null; + w.emitError(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + Sinks.Many w = window; + if (w != null) { + window = null; + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + + actual.onComplete(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + long u = Operators.multiplyCap(size, n); + s.request(u); + } + } + + @Override + public void cancel() { + if (CANCELLED.compareAndSet(this, 0, 1)) { + dispose(); + } + } + + @Override + public void dispose() { + if (WINDOW_COUNT.decrementAndGet(this) == 0) { + s.cancel(); + } + } + + @Override + public CoreSubscriber> actual() { + return actual; + } + + @Override + public boolean isDisposed() { + return cancelled == 1 || done; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return cancelled == 1; + if (key == Attr.CAPACITY) return size; + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(window)); + } + } + + static final class WindowSkipSubscriber + implements Disposable, InnerOperator> { + + final CoreSubscriber> actual; + final Context ctx; + + final Supplier> processorQueueSupplier; + + final int size; + + final int skip; + + volatile int cancelled; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater CANCELLED = + AtomicIntegerFieldUpdater.newUpdater(WindowSkipSubscriber.class, "cancelled"); + + volatile int windowCount; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WINDOW_COUNT = + AtomicIntegerFieldUpdater.newUpdater(WindowSkipSubscriber.class, "windowCount"); + + volatile int firstRequest; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater FIRST_REQUEST = + AtomicIntegerFieldUpdater.newUpdater(WindowSkipSubscriber.class, + "firstRequest"); + + int index; + + Subscription s; + + Sinks.Many window; + + boolean done; + + WindowSkipSubscriber(CoreSubscriber> actual, + int size, + int skip, + Supplier> processorQueueSupplier) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.size = size; + this.skip = skip; + this.processorQueueSupplier = processorQueueSupplier; + WINDOW_COUNT.lazySet(this, 1); + } + + @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, ctx); + return; + } + + int i = index; + + Sinks.Many w = window; + if (i == 0) { + WINDOW_COUNT.getAndIncrement(this); + + w = Sinks.unsafe().many().unicast().onBackpressureBuffer(processorQueueSupplier.get(), this); + window = w; + + actual.onNext(w.asFlux()); + } + + i++; + + if (w != null) { + w.emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + else { + Operators.onDiscard(t, ctx); + } + + if (i == size) { + window = null; + if (w != null) { + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + } + + if (i == skip) { + index = 0; + } + else { + index = i; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, ctx); + return; + } + done = true; + + Sinks.Many w = window; + if (w != null) { + window = null; + w.emitError(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + Sinks.Many w = window; + if (w != null) { + window = null; + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + + actual.onComplete(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + if (firstRequest == 0 && FIRST_REQUEST.compareAndSet(this, 0, 1)) { + long u = Operators.multiplyCap(size, n); + long v = Operators.multiplyCap(skip - size, n - 1); + long w = Operators.addCap(u, v); + s.request(w); + } + else { + long u = Operators.multiplyCap(skip, n); + s.request(u); + } + } + } + + @Override + public void cancel() { + if (CANCELLED.compareAndSet(this, 0, 1)) { + dispose(); + } + } + + @Override + public boolean isDisposed() { + return cancelled == 1 || done; + } + + @Override + public void dispose() { + if (WINDOW_COUNT.decrementAndGet(this) == 0) { + s.cancel(); + } + } + + @Override + public CoreSubscriber> actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return cancelled == 1; + if (key == Attr.CAPACITY) return size; + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(window)); + } + } + + static final class WindowOverlapSubscriber extends ArrayDeque> + implements Disposable, InnerOperator> { + + final CoreSubscriber> actual; + + final Supplier> processorQueueSupplier; + + final Queue> queue; + + final int size; + + final int skip; + + volatile int cancelled; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater CANCELLED = + AtomicIntegerFieldUpdater.newUpdater(WindowOverlapSubscriber.class, + "cancelled"); + + volatile int windowCount; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WINDOW_COUNT = + AtomicIntegerFieldUpdater.newUpdater(WindowOverlapSubscriber.class, + "windowCount"); + + volatile int firstRequest; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater FIRST_REQUEST = + AtomicIntegerFieldUpdater.newUpdater(WindowOverlapSubscriber.class, + "firstRequest"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(WindowOverlapSubscriber.class, + "requested"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(WindowOverlapSubscriber.class, "wip"); + + int index; + + int produced; + + Subscription s; + + volatile boolean done; + Throwable error; + + WindowOverlapSubscriber(CoreSubscriber> actual, + int size, + int skip, + Supplier> processorQueueSupplier, + Queue> overflowQueue) { + this.actual = actual; + this.size = size; + this.skip = skip; + this.processorQueueSupplier = processorQueueSupplier; + WINDOW_COUNT.lazySet(this, 1); + this.queue = overflowQueue; + } + + @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; + } + + int i = index; + + if (i == 0) { + if (cancelled == 0) { + WINDOW_COUNT.getAndIncrement(this); + + Sinks.Many w = Sinks.unsafe().many().unicast().onBackpressureBuffer(processorQueueSupplier.get(), this); + + offer(w); + + queue.offer(w); + drain(); + } + } + + i++; + + for (Sinks.Many w : this) { + w.emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + + int p = produced + 1; + if (p == size) { + produced = p - skip; + + Sinks.Many w = poll(); + if (w != null) { + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + } + else { + produced = p; + } + + if (i == skip) { + index = 0; + } + else { + index = i; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + + for (Sinks.Many w : this) { + w.emitError(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + clear(); + + error = t; + drain(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + for (Sinks.Many w : this) { + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + clear(); + + drain(); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + final Subscriber> a = actual; + final Queue> q = queue; + int missed = 1; + + for (; ; ) { + + long r = requested; + long e = 0; + + while (e != r) { + boolean d = done; + + Sinks.Many t = q.poll(); + + boolean empty = t == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t.asFlux()); + + e++; + } + + if (e == r) { + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + } + + if (e != 0L && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue q) { + if (cancelled == 1) { + q.clear(); + return true; + } + + if (d) { + Throwable e = error; + + if (e != null) { + q.clear(); + a.onError(e); + return true; + } + else if (empty) { + a.onComplete(); + return true; + } + } + + return false; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + + Operators.addCap(REQUESTED, this, n); + + if (firstRequest == 0 && FIRST_REQUEST.compareAndSet(this, 0, 1)) { + long u = Operators.multiplyCap(skip, n - 1); + long v = Operators.addCap(size, u); + s.request(v); + } + else { + long u = Operators.multiplyCap(skip, n); + s.request(u); + } + + drain(); + } + } + + @Override + public void cancel() { + if (CANCELLED.compareAndSet(this, 0, 1)) { + dispose(); + } + } + + @Override + public void dispose() { + if (WINDOW_COUNT.decrementAndGet(this) == 0) { + s.cancel(); + } + } + + @Override + public CoreSubscriber> actual() { + return actual; + } + + @Override + public boolean isDisposed() { + return cancelled == 1 || done; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return cancelled == 1; + if (key == Attr.CAPACITY) return size; + if (key == Attr.TERMINATED) return done; + if (key == Attr.LARGE_BUFFERED) return (long) queue.size() + size(); + if (key == Attr.BUFFERED) { + long realBuffered = (long) queue.size() + size(); + if (realBuffered < Integer.MAX_VALUE) return (int) realBuffered; + return Integer.MIN_VALUE; + } + if (key == Attr.ERROR) return error; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(toArray()) + .map(Scannable::from); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowBoundary.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowBoundary.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowBoundary.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Supplier; +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.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * Splits the source sequence into continuous, non-overlapping windowEnds + * where the window boundary is signalled by another Publisher + * + * @param the input value type + * @param the boundary publisher's type (irrelevant) + * @see Reactive-Streams-Commons + */ +final class FluxWindowBoundary extends InternalFluxOperator> { + + final Publisher other; + + final Supplier> processorQueueSupplier; + + FluxWindowBoundary(Flux source, Publisher other, + Supplier> processorQueueSupplier) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + this.processorQueueSupplier = Objects.requireNonNull(processorQueueSupplier, "processorQueueSupplier"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + @Nullable + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + WindowBoundaryMain main = new WindowBoundaryMain<>(actual, + processorQueueSupplier, processorQueueSupplier.get()); + + actual.onSubscribe(main); + + if (main.emit(main.window)) { + other.subscribe(main.boundary); + + return main; + } + else { + return null; + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class WindowBoundaryMain + implements InnerOperator>, Disposable { + + final Supplier> processorQueueSupplier; + + final WindowBoundaryOther boundary; + + final Queue queue; + final CoreSubscriber> actual; + + Sinks.Many window; + + volatile Subscription s; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(WindowBoundaryMain.class, Subscription.class, "s"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(WindowBoundaryMain.class, "requested"); + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(WindowBoundaryMain.class, Throwable.class, "error"); + + volatile int cancelled; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater CANCELLED = + AtomicIntegerFieldUpdater.newUpdater(WindowBoundaryMain.class, "cancelled"); + + volatile int windowCount; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WINDOW_COUNT = + AtomicIntegerFieldUpdater.newUpdater(WindowBoundaryMain.class, "windowCount"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(WindowBoundaryMain.class, "wip"); + + boolean done; + + static final Object BOUNDARY_MARKER = new Object(); + + static final Object DONE = new Object(); + + WindowBoundaryMain(CoreSubscriber> actual, + Supplier> processorQueueSupplier, + Queue processorQueue) { + this.actual = actual; + this.processorQueueSupplier = processorQueueSupplier; + this.window = Sinks.unsafe().many().unicast().onBackpressureBuffer(processorQueue, this); + WINDOW_COUNT.lazySet(this, 2); + this.boundary = new WindowBoundaryOther<>(this); + this.queue = Queues.unboundedMultiproducer().get(); + } + + @Override + public final CoreSubscriber> actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ERROR) return error; + if (key == Attr.CANCELLED) return cancelled == 1; + if (key == Attr.TERMINATED) return done; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.BUFFERED) return queue.size(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(boundary, Scannable.from(window)); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + synchronized (this) { + queue.offer(t); + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + boundary.cancel(); + if (Exceptions.addThrowable(ERROR, this, t)) { + drain(); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + boundary.cancel(); + synchronized (this) { + queue.offer(DONE); + } + drain(); + } + + @Override + public void dispose() { + if (WINDOW_COUNT.decrementAndGet(this) == 0) { + cancelMain(); + boundary.cancel(); + } + } + + @Override + public boolean isDisposed() { + return cancelled == 1 || done; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + void cancelMain() { + Operators.terminate(S, this); + } + + @Override + public void cancel() { + if (CANCELLED.compareAndSet(this, 0, 1)) { + dispose(); + } + } + + void boundaryNext() { + synchronized (this) { + queue.offer(BOUNDARY_MARKER); + } + + if (cancelled != 0) { + boundary.cancel(); + } + + drain(); + } + + void boundaryError(Throwable e) { + cancelMain(); + if (Exceptions.addThrowable(ERROR, this, e)) { + drain(); + } else { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + void boundaryComplete() { + cancelMain(); + synchronized (this) { + queue.offer(DONE); + } + drain(); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + final Subscriber> a = actual; + final Queue q = queue; + Sinks.Many w = window; + + int missed = 1; + + for (;;) { + + for (;;) { + if (error != null) { + q.clear(); + Throwable e = Exceptions.terminate(ERROR, this); + if (e != Exceptions.TERMINATED) { + w.emitError(e, Sinks.EmitFailureHandler.FAIL_FAST); + + a.onError(e); + } + return; + } + + Object o = q.poll(); + + if (o == null) { + break; + } + + if (o == DONE) { + q.clear(); + + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + + a.onComplete(); + return; + } + if (o != BOUNDARY_MARKER) { + + @SuppressWarnings("unchecked") + T v = (T)o; + w.emitNext(v, Sinks.EmitFailureHandler.FAIL_FAST); + } + if (o == BOUNDARY_MARKER) { + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + + if (cancelled == 0) { + if (requested != 0L) { + Queue pq = processorQueueSupplier.get(); + + WINDOW_COUNT.getAndIncrement(this); + + w = Sinks.unsafe().many().unicast().onBackpressureBuffer(pq, this); + window = w; + + a.onNext(w.asFlux()); + + if (requested != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + } else { + q.clear(); + cancelMain(); + boundary.cancel(); + + a.onError(Exceptions.failWithOverflow("Could not create new window due to lack of requests")); + return; + } + } + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean emit(Sinks.Many w) { + long r = requested; + if (r != 0L) { + actual.onNext(w.asFlux()); + if (r != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + return true; + } else { + cancel(); + + actual.onError(Exceptions.failWithOverflow("Could not emit buffer due to lack of requests")); + + return false; + } + } + } + + static final class WindowBoundaryOther + extends Operators.DeferredSubscription + implements InnerConsumer { + + final WindowBoundaryMain main; + + WindowBoundaryOther(WindowBoundaryMain main) { + this.main = main; + } + + @Override + public void onSubscribe(Subscription s) { + if (set(s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) { + return main; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return super.scanUnsafe(key); + } + + @Override + public void onNext(U t) { + main.boundaryNext(); + } + + @Override + public void onError(Throwable t) { + main.boundaryError(t); + } + + @Override + public void onComplete() { + main.boundaryComplete(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowPredicate.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowPredicate.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowPredicate.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,923 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +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.FluxBufferPredicate.Mode; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * 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: + *
    + *
  • {@code Until}: A new window starts when the predicate returns true. The + * element that just matched the predicate is the last in the previous window, and the + * windows are not emitted before an inner element is pushed.
  • + *
  • {@code UntilOther}: A new window starts when the predicate returns true. The + * element that just matched the predicate is the first in the new window, which is + * emitted immediately.
  • + *
  • {@code While}: A new window starts when the predicate stops matching. The + * non-matching elements that delimit each window are simply discarded, and the + * windows are not emitted before an inner element is pushed
  • + *
+ * + * @param the source and window value type + * + * @see Reactive-Streams-Commons + */ +final class FluxWindowPredicate extends InternalFluxOperator> + implements Fuseable{ + + final Supplier> groupQueueSupplier; + + final Supplier>> mainQueueSupplier; + + final Mode mode; + + final Predicate predicate; + + final int prefetch; + + FluxWindowPredicate(Flux source, + Supplier>> mainQueueSupplier, + Supplier> groupQueueSupplier, + int prefetch, + Predicate predicate, + Mode mode) { + super(source); + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.predicate = Objects.requireNonNull(predicate, "predicate"); + this.mainQueueSupplier = + Objects.requireNonNull(mainQueueSupplier, "mainQueueSupplier"); + this.groupQueueSupplier = + Objects.requireNonNull(groupQueueSupplier, "groupQueueSupplier"); + this.mode = mode; + this.prefetch = prefetch; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new WindowPredicateMain<>(actual, + mainQueueSupplier.get(), + groupQueueSupplier, + prefetch, + predicate, + mode); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class WindowPredicateMain + implements Fuseable.QueueSubscription>, + InnerOperator> { + + final CoreSubscriber> actual; + final Context ctx; + + final Supplier> groupQueueSupplier; + + final Mode mode; + + final Predicate predicate; + + final int prefetch; + + final Queue> queue; + + WindowFlux window; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(WindowPredicateMain.class, "wip"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(WindowPredicateMain.class, "requested"); + + volatile boolean done; + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(WindowPredicateMain.class, + Throwable.class, + "error"); + + volatile int cancelled; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater CANCELLED = + AtomicIntegerFieldUpdater.newUpdater(WindowPredicateMain.class, + "cancelled"); + + volatile int windowCount; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WINDOW_COUNT = + AtomicIntegerFieldUpdater.newUpdater(WindowPredicateMain.class, "windowCount"); + + Subscription s; + + volatile boolean outputFused; + + WindowPredicateMain(CoreSubscriber> actual, + Queue> queue, + Supplier> groupQueueSupplier, + int prefetch, + Predicate predicate, + Mode mode) { + this.actual = actual; + this.ctx = actual.currentContext(); + this.queue = queue; + this.groupQueueSupplier = groupQueueSupplier; + this.prefetch = prefetch; + this.predicate = predicate; + this.mode = mode; + WINDOW_COUNT.lazySet(this, 2); + initializeWindow(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + if (cancelled == 0) { + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + } + + void initializeWindow() { + WindowFlux g = new WindowFlux<>( + groupQueueSupplier.get(), + this); + window = g; + } + + @Nullable WindowFlux newWindowDeferred() { + // if the main is cancelled, don't create new groups + if (cancelled == 0) { + WINDOW_COUNT.getAndIncrement(this); + + WindowFlux g = new WindowFlux<>(groupQueueSupplier.get(), this); + window = g; + return g; + } + return null; + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, ctx); + return; + } + WindowFlux g = window; + + boolean match; + try { + match = predicate.test(t); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, ctx)); + return; + } + + + if (!handleDeferredWindow(g, t)) { + return; + } + drain(); + + if (mode == Mode.UNTIL && match) { + if(g.cancelled) { + // so it's consistent with the previously discarded elements when (mode == UNTIL && !match) was satisfied + Operators.onDiscard(t, ctx); + s.request(1); + } else { + g.onNext(t); + } + g.onComplete(); + newWindowDeferred(); + s.request(1); + } + else if (mode == Mode.UNTIL_CUT_BEFORE && match) { + g.onComplete(); + g = newWindowDeferred(); + if (g != null) { + g.onNext(t); + handleDeferredWindow(g, t); + drain(); + } + } + else if (mode == Mode.WHILE && !match) { + g.onComplete(); + newWindowDeferred(); + Operators.onDiscard(t, ctx); + //compensate for the dropped delimiter + s.request(1); + } + else if(g.cancelled) { + Operators.onDiscard(t, ctx); + s.request(1); + } + else { + g.onNext(t); + } + } + + boolean handleDeferredWindow(@Nullable WindowFlux window, T signal) { + if (window != null && window.deferred) { + window.deferred = false; + if (!queue.offer(window)) { + onError(Operators.onOperatorError(this, + Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), + signal, + ctx)); + return false; + } + } + return true; + } + + @Override + public void onError(Throwable t) { + if (Exceptions.addThrowable(ERROR, this, t)) { + done = true; + cleanup(); + drain(); + } + else { + Operators.onErrorDropped(t, ctx); + } + } + + @Override + public void onComplete() { + if(done) { + return; + } + cleanup(); + + WindowFlux g = window; + if (g != null) { + g.onComplete(); + } + window = null; + done = true; + WINDOW_COUNT.decrementAndGet(this); + drain(); + } + + void cleanup() { + // necessary cleanup if predicate contains a state + if (predicate instanceof Disposable) { + ((Disposable) predicate).dispose(); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return cancelled == 1; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.TERMINATED) return done; + if (key == Attr.BUFFERED) return queue.size(); + if (key == Attr.ERROR) return error; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return window == null ? Stream.empty() : Stream.of(window); + } + + @Override + public CoreSubscriber> actual() { + return actual; + } + + void signalAsyncError() { + Throwable e = Exceptions.terminate(ERROR, this); + windowCount = 0; + WindowFlux g = window; + if (g != null) { + g.onError(e); + } + actual.onError(e); + window = null; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (CANCELLED.compareAndSet(this, 0, 1)) { + if (WINDOW_COUNT.decrementAndGet(this) == 0) { + s.cancel(); + cleanup(); + } + else if (!outputFused) { + if (WIP.getAndIncrement(this) == 0) { + // remove queued up but unobservable groups from the mapping + Flux g; + WindowFlux w = window; + while ((g = queue.poll()) != null) { + ((WindowFlux) g).cancel(); + } + if (w != null && w.deferred) { + w.cancel(); + } + + if (WIP.decrementAndGet(this) == 0) { + if (!done && WINDOW_COUNT.get(this) == 0) { + s.cancel(); + cleanup(); + } + else { + CANCELLED.set(this, 2); + } + return; + } + + CANCELLED.set(this, 2); //in flight windows might get cancelled still + + drainLoop(); + } + } + } + else if (CANCELLED.get(this) == 2) { + //no new window should have been created + if (WINDOW_COUNT.get(this) == 0) { + s.cancel(); + cleanup(); + } + //next one to call cancel in state 2 that decrements to 0 will cancel outer + } + } + + void groupTerminated() { + if (windowCount == 0) { + return; + } + window = null; + if (WINDOW_COUNT.decrementAndGet(this) == 0) { + s.cancel(); + cleanup(); + } + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + if (outputFused) { + drainFused(); + } + else { + drainLoop(); + } + } + + void drainFused() { + int missed = 1; + + final Subscriber> a = actual; + final Queue> q = queue; + + for (; ; ) { + + if (cancelled != 0) { + q.clear(); + return; + } + + boolean d = done; + + a.onNext(null); + + if (d) { + Throwable ex = error; + if (ex != null) { + signalAsyncError(); + } + else { + a.onComplete(); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drainLoop() { + + int missed = 1; + + Subscriber> a = actual; + Queue> q = queue; + + for (; ; ) { + + long r = requested; + long e = 0L; + + while (e != r) { + boolean d = done; + Flux v = q.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(v); + + e++; + } + + if (e == r) { + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + } + + if (e != 0L) { + + s.request(e); + + if (r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean checkTerminated(boolean d, + boolean empty, + Subscriber a, + Queue> q) { + + if (cancelled != 0) { + q.clear(); + return true; + } + if (d) { + Throwable e = error; + if (e != null && e != Exceptions.TERMINATED) { + queue.clear(); + signalAsyncError(); + return true; + } + else if (empty) { + a.onComplete(); + return true; + } + } + + return false; + } + + @Override + @Nullable + public Flux poll() { + return queue.poll(); + } + + @Override + public int size() { + return queue.size(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & Fuseable.ASYNC) != 0) { + outputFused = true; + return Fuseable.ASYNC; + } + return Fuseable.NONE; + } + } + + static final class WindowFlux extends Flux + implements Fuseable, Fuseable.QueueSubscription, InnerOperator { + + final Queue queue; + + volatile WindowPredicateMain parent; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + PARENT = AtomicReferenceFieldUpdater.newUpdater(WindowFlux.class, + WindowPredicateMain.class, + "parent"); + + volatile boolean done; + Throwable error; + + volatile CoreSubscriber actual; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ACTUAL = + AtomicReferenceFieldUpdater.newUpdater(WindowFlux.class, + CoreSubscriber.class, + "actual"); + + volatile Context ctx = Context.empty(); + + volatile boolean cancelled; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(WindowFlux.class, "once"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(WindowFlux.class, "wip"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(WindowFlux.class, "requested"); + + volatile boolean enableOperatorFusion; + + int produced; + + boolean deferred; + + WindowFlux( + Queue queue, + WindowPredicateMain parent) { + this.queue = queue; + this.parent = parent; + this.deferred = true; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + void propagateTerminate() { + WindowPredicateMain r = parent; + if (r != null && PARENT.compareAndSet(this, r, null)) { + r.groupTerminated(); + } + } + + void drainRegular(Subscriber 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)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + e++; + } + + if (r == e) { + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + } + + if (e != 0) { + WindowPredicateMain main = parent; + if (main != null) { + main.s.request(e); + } + if (r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drainFused(Subscriber a) { + int missed = 1; + + final Queue q = queue; + + for (; ; ) { + + if (cancelled) { + Operators.onDiscardQueueWithClear(q, ctx, null); + ctx = Context.empty(); + actual = null; + return; + } + + boolean d = done; + + a.onNext(null); + + if (d) { + ctx = Context.empty(); + actual = null; + + Throwable ex = error; + if (ex != null) { + a.onError(ex); + } + else { + a.onComplete(); + } + return; + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + void drain() { + Subscriber a = actual; + if (a != null) { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + if (enableOperatorFusion) { + drainFused(a); + } + else { + drainRegular(a); + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue q) { + if (cancelled) { + Operators.onDiscardQueueWithClear(q, ctx, null); + ctx = Context.empty(); + actual = null; + return true; + } + if (d && empty) { + Throwable e = error; + ctx = Context.empty(); + actual = null; + if (e != null) { + a.onError(e); + } + else { + a.onComplete(); + } + return true; + } + + return false; + } + + public void onNext(T t) { + Subscriber a = actual; + + if (!queue.offer(t)) { + onError(Operators.onOperatorError(this, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t, + ctx)); + return; + } + if (enableOperatorFusion) { + if (a != null) { + a.onNext(null); // in op-fusion, onNext(null) is the indicator of more data + } + } + else { + drain(); + } + } + + @Override + public void onSubscribe(Subscription s) { + //IGNORE + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + + propagateTerminate(); + + drain(); + } + + @Override + public void onComplete() { + done = true; + propagateTerminate(); + + drain(); + } + + @Override + public void subscribe(CoreSubscriber actual) { + if (once == 0 && ONCE.compareAndSet(this, 0, 1)) { + actual.onSubscribe(this); + ACTUAL.lazySet(this, actual); + ctx = actual.currentContext(); + drain(); + } + else { + actual.onError(new IllegalStateException( + "This processor allows only a single Subscriber")); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (cancelled) { + return; + } + cancelled = true; + + WindowPredicateMain r = parent; + if (r != null && + PARENT.compareAndSet(this, r, null)) { + + if (WindowPredicateMain.WINDOW_COUNT.decrementAndGet(r) == 0) { + r.cancel(); + } + else { + r.s.request(1); + } + } + + if (!enableOperatorFusion) { + if (WIP.getAndIncrement(this) == 0) { + Operators.onDiscardQueueWithClear(queue, ctx, null); + } + } + + } + + @Override + @Nullable + public T poll() { + T v = queue.poll(); + if (v != null) { + produced++; + } + else { + int p = produced; + if (p != 0) { + produced = 0; + WindowPredicateMain main = parent; + if (main != null) { + main.s.request(p); + } + } + } + return v; + } + + @Override + public int size() { + return queue.size(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + public void clear() { + Operators.onDiscardQueueWithClear(queue, ctx, null); + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & Fuseable.ASYNC) != 0) { + enableOperatorFusion = true; + return Fuseable.ASYNC; + } + return Fuseable.NONE; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.TERMINATED) return done; + if (key == Attr.BUFFERED) return queue == null ? 0 : queue.size(); + if (key == Attr.ERROR) return error; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowTimeout.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowTimeout.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowTimeout.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2017-2021 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.TimeUnit; +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.Disposables; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.scheduler.Scheduler; +import reactor.util.concurrent.Queues; + +/** + * @author David Karnok + */ +final class FluxWindowTimeout extends InternalFluxOperator> { + + final int maxSize; + final long timespan; + final TimeUnit unit; + final Scheduler timer; + + FluxWindowTimeout(Flux source, int maxSize, long timespan, TimeUnit unit, Scheduler timer) { + 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.timer = Objects.requireNonNull(timer, "Timer"); + this.timespan = timespan; + this.unit = Objects.requireNonNull(unit, "unit"); + this.maxSize = maxSize; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + 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; + + return super.scanUnsafe(key); + } + + static final class WindowTimeoutSubscriber implements InnerOperator> { + + final CoreSubscriber> actual; + final long timespan; + final TimeUnit unit; + final Scheduler scheduler; + final int maxSize; + final Scheduler.Worker worker; + final Queue queue; + + Throwable error; + volatile boolean done; + volatile boolean cancelled; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(WindowTimeoutSubscriber.class, + "requested"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(WindowTimeoutSubscriber.class, + "wip"); + + int count; + long producerIndex; + + Subscription s; + + Sinks.Many window; + + volatile boolean terminated; + + volatile Disposable timer; + @SuppressWarnings("rawtypes") + 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.timespan = timespan; + this.unit = unit; + this.scheduler = scheduler; + this.maxSize = maxSize; + this.worker = scheduler.createWorker(); + } + + @Override + public CoreSubscriber> actual() { + return actual; + } + + @Override + public Stream inners() { + Sinks.Many w = window; + return w == null ? Stream.empty() : Stream.of(Scannable.from(w)); + } + + @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; + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + Subscriber> a = actual; + + a.onSubscribe(this); + + if (cancelled) { + return; + } + + Sinks.Many w = Sinks.unsafe().many().unicast().onBackpressureBuffer(); + window = w; + + long r = requested; + if (r != 0L) { + a.onNext(w.asFlux()); + if (r != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + } + else { + a.onError(Operators.onOperatorError(s, + Exceptions.failWithOverflow(), actual.currentContext())); + return; + } + + if (OperatorDisposables.replace(TIMER, this, newPeriod())) { + s.request(Long.MAX_VALUE); + } + } + } + + Disposable newPeriod() { + try { + return worker.schedulePeriodically(new ConsumerIndexHolder(producerIndex, + this), timespan, timespan, unit); + } + catch (Exception e) { + actual.onError(Operators.onRejectedExecution(e, s, null, null, actual.currentContext())); + return Disposables.disposed(); + } + } + + @Override + public void onNext(T t) { + if (terminated) { + return; + } + + if (WIP.get(this) == 0 && WIP.compareAndSet(this, 0, 1)) { + Sinks.Many w = window; + w.emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST); + + int c = count + 1; + + if (c >= maxSize) { + producerIndex++; + count = 0; + + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + + long r = requested; + + if (r != 0L) { + w = Sinks.unsafe().many().unicast().onBackpressureBuffer(); + window = w; + actual.onNext(w.asFlux()); + if (r != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + + Disposable tm = timer; + tm.dispose(); + + Disposable task = newPeriod(); + + if (!TIMER.compareAndSet(this, tm, task)) { + task.dispose(); + } + } + else { + window = null; + actual.onError(Operators.onOperatorError(s, + Exceptions.failWithOverflow(), t, actual + .currentContext())); + timer.dispose(); + worker.dispose(); + return; + } + } + else { + count = c; + } + + if (WIP.decrementAndGet(this) == 0) { + return; + } + } + else { + queue.offer(t); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + if (enter()) { + drainLoop(); + } + + actual.onError(t); + timer.dispose(); + worker.dispose(); + } + + @Override + public void onComplete() { + done = true; + if (enter()) { + drainLoop(); + } + + actual.onComplete(); + timer.dispose(); + worker.dispose(); + } + + @Override + public void request(long n) { + if(Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + cancelled = true; + } + + @SuppressWarnings("unchecked") + void drainLoop() { + final Queue q = queue; + final Subscriber> a = actual; + Sinks.Many w = window; + + int missed = 1; + for (; ; ) { + + for (; ; ) { + if (terminated) { + s.cancel(); + q.clear(); + timer.dispose(); + worker.dispose(); + return; + } + + boolean d = done; + + Object o = q.poll(); + + boolean empty = o == null; + boolean isHolder = o instanceof ConsumerIndexHolder; + + if (d && (empty || isHolder)) { + window = null; + q.clear(); + Throwable err = error; + if (err != null) { + w.emitError(err, Sinks.EmitFailureHandler.FAIL_FAST); + } + else { + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + timer.dispose(); + worker.dispose(); + return; + } + + if (empty) { + break; + } + + if (isHolder) { + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + count = 0; + w = Sinks.unsafe().many().unicast().onBackpressureBuffer(); + window = w; + + long r = requested; + if (r != 0L) { + a.onNext(w.asFlux()); + if (r != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + } + else { + window = null; + queue.clear(); + a.onError(Operators.onOperatorError(s, + Exceptions.failWithOverflow(), actual.currentContext())); + timer.dispose(); + worker.dispose(); + return; + } + continue; + } + + w.emitNext((T) o, Sinks.EmitFailureHandler.FAIL_FAST); + int c = count + 1; + + if (c >= maxSize) { + producerIndex++; + count = 0; + + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + + long r = requested; + + if (r != 0L) { + w = Sinks.unsafe().many().unicast().onBackpressureBuffer(); + window = w; + actual.onNext(w.asFlux()); + if (r != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + + Disposable tm = timer; + tm.dispose(); + + Disposable task = newPeriod(); + + if (!TIMER.compareAndSet(this, tm, task)) { + task.dispose(); + } + } + else { + window = null; + a.onError(Operators.onOperatorError(s, + Exceptions.failWithOverflow(), o, actual + .currentContext())); + timer.dispose(); + worker.dispose(); + return; + } + } + else { + count = c; + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean enter() { + return WIP.getAndIncrement(this) == 0; + } + + static final class ConsumerIndexHolder implements Runnable { + + final long index; + final WindowTimeoutSubscriber parent; + + ConsumerIndexHolder(long index, WindowTimeoutSubscriber parent) { + this.index = index; + this.parent = parent; + } + + @Override + public void run() { + WindowTimeoutSubscriber p = parent; + + if (!p.cancelled) { + p.queue.offer(this); + } + else { + p.terminated = true; + p.timer.dispose(); + p.worker.dispose(); + } + if (p.enter()) { + p.drainLoop(); + } + } + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWindowWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2016-2021 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.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.reactivestreams.Publisher; +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.util.annotation.Nullable; +import reactor.util.concurrent.Queues; + +/** + * Splits the source sequence into potentially overlapping windowEnds controlled by items + * of a start Publisher and end Publishers derived from the start values. + * + * @param the source value type + * @param the window starter value type + * @param the window end value type (irrelevant) + * + * @see Reactive-Streams-Commons + */ +final class FluxWindowWhen extends InternalFluxOperator> { + + final Publisher start; + + final Function> end; + + final Supplier> processorQueueSupplier; + + FluxWindowWhen(Flux source, + Publisher start, + Function> end, + Supplier> processorQueueSupplier) { + super(source); + this.start = Objects.requireNonNull(start, "start"); + this.end = Objects.requireNonNull(end, "end"); + this.processorQueueSupplier = + Objects.requireNonNull(processorQueueSupplier, "processorQueueSupplier"); + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + @Nullable + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + WindowWhenMainSubscriber main = new WindowWhenMainSubscriber<>(actual, + start, end, processorQueueSupplier); + actual.onSubscribe(main); + + if (main.cancelled) { + return null; + } + WindowWhenOpenSubscriber os = new WindowWhenOpenSubscriber<>(main); + + if (WindowWhenMainSubscriber.BOUNDARY.compareAndSet(main,null, os)) { + WindowWhenMainSubscriber.OPEN_WINDOW_COUNT.incrementAndGet(main); + start.subscribe(os); + return main; + } + else { + return null; + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class WindowWhenMainSubscriber + extends QueueDrainSubscriber> { + + final Publisher open; + final Function> close; + final Supplier> processorQueueSupplier; + final Disposable.Composite resources; + + Subscription s; + + volatile Disposable boundary; + static final AtomicReferenceFieldUpdater BOUNDARY = + AtomicReferenceFieldUpdater.newUpdater(WindowWhenMainSubscriber.class, Disposable.class, "boundary"); + + final List> windows; + + volatile long openWindowCount; + static final AtomicLongFieldUpdater OPEN_WINDOW_COUNT = + AtomicLongFieldUpdater.newUpdater(WindowWhenMainSubscriber.class, "openWindowCount"); + + WindowWhenMainSubscriber(CoreSubscriber> actual, + Publisher open, Function> close, + Supplier> processorQueueSupplier) { + super(actual, Queues.unboundedMultiproducer().get()); + this.open = open; + this.close = close; + this.processorQueueSupplier = processorQueueSupplier; + this.resources = Disposables.composite(); + this.windows = new ArrayList<>(); + OPEN_WINDOW_COUNT.lazySet(this, 1); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + if (fastEnter()) { + for (Sinks.Many w : windows) { + w.emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + if (leave(-1) == 0) { + return; + } + } else { + queue.offer(t); + if (!enter()) { + return; + } + } + drainLoop(); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + error = t; + done = true; + + if (enter()) { + drainLoop(); + } + + if (OPEN_WINDOW_COUNT.decrementAndGet(this) == 0) { + resources.dispose(); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + if (enter()) { + drainLoop(); + } + + if (OPEN_WINDOW_COUNT.decrementAndGet(this) == 0) { + resources.dispose(); + } + } + + void error(Throwable t) { + s.cancel(); + resources.dispose(); + OperatorDisposables.dispose(BOUNDARY, this); + + actual.onError(t); + } + + @Override + public void request(long n) { + requested(n); + } + + @Override + public void cancel() { + cancelled = true; + } + + void dispose() { + resources.dispose(); + OperatorDisposables.dispose(BOUNDARY, this); + } + + void drainLoop() { + final Queue q = queue; + final Subscriber> a = actual; + final List> ws = this.windows; + int missed = 1; + + for (;;) { + + for (;;) { + boolean d = done; + Object o = q.poll(); + + boolean empty = o == null; + + if (d && empty) { + dispose(); + Throwable e = error; + if (e != null) { + actual.onError(e); + for (Sinks.Many w : ws) { + w.emitError(e, Sinks.EmitFailureHandler.FAIL_FAST); + } + } else { + actual.onComplete(); + for (Sinks.Many w : ws) { + w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + } + } + ws.clear(); + return; + } + + if (empty) { + break; + } + + if (o instanceof WindowOperation) { + @SuppressWarnings("unchecked") + WindowOperation wo = (WindowOperation) o; + + Sinks.Many w = wo.w; + if (w != null) { + if (ws.remove(wo.w)) { + wo.w.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST); + + if (OPEN_WINDOW_COUNT.decrementAndGet(this) == 0) { + dispose(); + return; + } + } + continue; + } + + if (cancelled) { + continue; + } + + + w = Sinks.unsafe().many().unicast().onBackpressureBuffer(processorQueueSupplier.get()); + + long r = requested(); + if (r != 0L) { + ws.add(w); + a.onNext(w.asFlux()); + if (r != Long.MAX_VALUE) { + produced(1); + } + } else { + cancelled = true; + a.onError(Exceptions.failWithOverflow("Could not deliver new window due to lack of requests")); + continue; + } + + Publisher p; + + try { + p = Objects.requireNonNull(close.apply(wo.open), "The publisher supplied is null"); + } catch (Throwable e) { + cancelled = true; + a.onError(e); + continue; + } + + WindowWhenCloseSubscriber cl = new WindowWhenCloseSubscriber(this, w); + + if (resources.add(cl)) { + OPEN_WINDOW_COUNT.getAndIncrement(this); + + p.subscribe(cl); + } + + continue; + } + + for (Sinks.Many w : ws) { + @SuppressWarnings("unchecked") + T t = (T) o; + w.emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + } + + missed = leave(-missed); + if (missed == 0) { + break; + } + } + } + + void open(U b) { + queue.offer(new WindowOperation(null, b)); + if (enter()) { + drainLoop(); + } + } + + void close(WindowWhenCloseSubscriber w) { + resources.remove(w); + queue.offer(new WindowOperation(w.w, null)); + if (enter()) { + drainLoop(); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + } + + static final class WindowOperation { + final Sinks.Many w; + final U open; + WindowOperation(@Nullable Sinks.Many w, @Nullable U open) { + this.w = w; + this.open = open; + } + } + + static final class WindowWhenOpenSubscriber + implements Disposable, Subscriber { + + volatile Subscription subscription; + static final AtomicReferenceFieldUpdater SUBSCRIPTION = + AtomicReferenceFieldUpdater.newUpdater(WindowWhenOpenSubscriber.class, Subscription.class, "subscription"); + + final WindowWhenMainSubscriber parent; + + boolean done; + + WindowWhenOpenSubscriber(WindowWhenMainSubscriber parent) { + this.parent = parent; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(SUBSCRIPTION, this, s)) { + subscription.request(Long.MAX_VALUE); + } + } + + @Override + public void dispose() { + Operators.terminate(SUBSCRIPTION, this); + } + + @Override + public boolean isDisposed() { + return subscription == Operators.cancelledSubscription(); + } + + @Override + public void onNext(U t) { + if (done) { + return; + } + parent.open(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, parent.actual.currentContext()); + return; + } + done = true; + parent.error(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.onComplete(); + } + } + + static final class WindowWhenCloseSubscriber + implements Disposable, Subscriber { + + volatile Subscription subscription; + static final AtomicReferenceFieldUpdater SUBSCRIPTION = + AtomicReferenceFieldUpdater.newUpdater(WindowWhenCloseSubscriber.class, Subscription.class, "subscription"); + + final WindowWhenMainSubscriber parent; + final Sinks.Many w; + + boolean done; + + WindowWhenCloseSubscriber(WindowWhenMainSubscriber parent, Sinks.Many w) { + this.parent = parent; + this.w = w; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(SUBSCRIPTION, this, s)) { + subscription.request(Long.MAX_VALUE); + } + } + + @Override + public void dispose() { + Operators.terminate(SUBSCRIPTION, this); + } + + @Override + public boolean isDisposed() { + return subscription == Operators.cancelledSubscription(); + } + + @Override + public void onNext(V t) { + if (done) { + return; + } + done = true; + dispose(); + parent.close(this); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, parent.actual.currentContext()); + return; + } + done = true; + parent.error(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.close(this); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxWithLatestFrom.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxWithLatestFrom.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxWithLatestFrom.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2016-2021 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.AtomicReferenceFieldUpdater; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +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; + +/** + * Combines values from a main Publisher with values from another + * Publisher through a bi-function and emits the result. + *

+ *

+ * The operator will drop values from the main source until the other + * Publisher produces any value. + *

+ * If the other Publisher completes without any value, the sequence is completed. + * + * @param the main source type + * @param the alternate source type + * @param the output type + * + * @see Reactive-Streams-Commons + */ +final class FluxWithLatestFrom extends InternalFluxOperator { + + final Publisher other; + + final BiFunction combiner; + + FluxWithLatestFrom(Flux source, + Publisher other, + BiFunction combiner) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + this.combiner = Objects.requireNonNull(combiner, "combiner"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CoreSubscriber serial = Operators.serialize(actual); + + WithLatestFromSubscriber main = + new WithLatestFromSubscriber<>(serial, combiner); + + WithLatestFromOtherSubscriber secondary = + new WithLatestFromOtherSubscriber<>(main); + + other.subscribe(secondary); + + return main; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == RUN_STYLE) return SYNC; + return super.scanUnsafe(key); + } + + static final class WithLatestFromSubscriber implements InnerOperator { + + final CoreSubscriber actual; + final BiFunction combiner; + + volatile Subscription main; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + MAIN = + AtomicReferenceFieldUpdater.newUpdater(WithLatestFromSubscriber.class, + Subscription.class, + "main"); + + volatile Subscription other; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + OTHER = + AtomicReferenceFieldUpdater.newUpdater(WithLatestFromSubscriber.class, + Subscription.class, + "other"); + + volatile U otherValue; + + WithLatestFromSubscriber(CoreSubscriber actual, + BiFunction combiner) { + this.actual = actual; + this.combiner = combiner; + } + + void setOther(Subscription s) { + if (!OTHER.compareAndSet(this, null, s)) { + s.cancel(); + if (other != Operators.cancelledSubscription()) { + Operators.reportSubscriptionSet(); + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return main == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return main; + if (key == RUN_STYLE) return SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(other)); + } + + @Override + public void request(long n) { + main.request(n); + } + + void cancelMain() { + Subscription s = main; + if (s != Operators.cancelledSubscription()) { + s = MAIN.getAndSet(this, Operators.cancelledSubscription()); + if (s != null && s != Operators.cancelledSubscription()) { + s.cancel(); + } + } + } + + void cancelOther() { + Subscription s = other; + if (s != Operators.cancelledSubscription()) { + s = OTHER.getAndSet(this, Operators.cancelledSubscription()); + if (s != null && s != Operators.cancelledSubscription()) { + s.cancel(); + } + } + } + + @Override + public void cancel() { + cancelMain(); + cancelOther(); + } + + @Override + public void onSubscribe(Subscription s) { + if (!MAIN.compareAndSet(this, null, s)) { + s.cancel(); + if (main != Operators.cancelledSubscription()) { + Operators.reportSubscriptionSet(); + } + } + else { + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + U u = otherValue; + + if (u != null) { + R r; + + try { + r = Objects.requireNonNull(combiner.apply(t, u), + "The combiner returned a null value"); + } + catch (Throwable e) { + onError(Operators.onOperatorError(this, e, t, actual.currentContext())); + return; + } + + actual.onNext(r); + } + else { + main.request(1); + } + } + + @Override + public void onError(Throwable t) { + if (main == null) { + if (MAIN.compareAndSet(this, null, Operators.cancelledSubscription())) { + cancelOther(); + + Operators.error(actual, t); + return; + } + } + cancelOther(); + + otherValue = null; + actual.onError(t); + } + + @Override + public void onComplete() { + cancelOther(); + + otherValue = null; + actual.onComplete(); + } + + void otherError(Throwable t) { + if (main == null) { + if (MAIN.compareAndSet(this, null, Operators.cancelledSubscription())) { + cancelMain(); + + Operators.error(actual, t); + return; + } + } + cancelMain(); + + otherValue = null; + actual.onError(t); + } + + void otherComplete() { + if (otherValue == null) { + if (main == null) { + if (MAIN.compareAndSet(this, + null, + Operators.cancelledSubscription())) { + cancelMain(); + + Operators.complete(actual); + return; + } + } + cancelMain(); + + actual.onComplete(); + } + } + } + + static final class WithLatestFromOtherSubscriber implements InnerConsumer { + + final WithLatestFromSubscriber main; + + WithLatestFromOtherSubscriber(WithLatestFromSubscriber main) { + this.main = main; + } + + @Override + public void onSubscribe(Subscription s) { + main.setOther(s); + + s.request(Long.MAX_VALUE); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL) { + return main; + } + if (key == RUN_STYLE) { + return SYNC; + } + return null; + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + public void onNext(U t) { + main.otherValue = t; + } + + @Override + public void onError(Throwable t) { + main.otherError(t); + } + + @Override + public void onComplete() { + main.otherComplete(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxZip.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxZip.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxZip.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,999 @@ +/* + * Copyright (c) 2016-2021 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.Arrays; +import java.util.Objects; +import java.util.Queue; +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.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +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.util.annotation.Nullable; +import reactor.util.context.Context; + +import static reactor.core.Fuseable.ASYNC; +import static reactor.core.Fuseable.SYNC; + +/** + * Repeatedly takes one item from all source Publishers and + * runs it through a function to produce the output item. + * + * @param the common input type + * @param the output value type + * + * @see Reactive-Streams-Commons + */ +final class FluxZip extends Flux implements SourceProducer { + + final Publisher[] sources; + + final Iterable> sourcesIterable; + + final Function zipper; + + final Supplier> queueSupplier; + + final int prefetch; + + @SuppressWarnings("unchecked") + FluxZip(Publisher p1, + Publisher p2, + BiFunction zipper2, + Supplier> queueSupplier, + int prefetch) { + this(new Publisher[]{Objects.requireNonNull(p1, "p1"), + Objects.requireNonNull(p2, "p2")}, + new PairwiseZipper<>(new BiFunction[]{ + Objects.requireNonNull(zipper2, "zipper2")}), + queueSupplier, + prefetch); + } + + FluxZip(Publisher[] sources, + Function zipper, + Supplier> queueSupplier, + int prefetch) { + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.sources = Objects.requireNonNull(sources, "sources"); + if (sources.length == 0) { + throw new IllegalArgumentException("at least one source is required"); + } + this.sourcesIterable = null; + this.zipper = Objects.requireNonNull(zipper, "zipper"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + this.prefetch = prefetch; + } + + FluxZip(Iterable> sourcesIterable, + Function zipper, + Supplier> queueSupplier, + int prefetch) { + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.sources = null; + this.sourcesIterable = Objects.requireNonNull(sourcesIterable, "sourcesIterable"); + this.zipper = Objects.requireNonNull(zipper, "zipper"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + this.prefetch = prefetch; + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Nullable + FluxZip zipAdditionalSource(Publisher source, BiFunction zipper) { + Publisher[] oldSources = sources; + if (oldSources != null && this.zipper instanceof PairwiseZipper) { + int oldLen = oldSources.length; + Publisher[] newSources = new Publisher[oldLen + 1]; + System.arraycopy(oldSources, 0, newSources, 0, oldLen); + newSources[oldLen] = source; + + Function z = ((PairwiseZipper) this.zipper).then(zipper); + + return new FluxZip<>(newSources, z, queueSupplier, prefetch); + } + return null; + } + + @Override + public void subscribe(CoreSubscriber actual) { + Publisher[] srcs = sources; + try { + if (srcs != null) { + handleArrayMode(actual, srcs); + } + else { + handleIterableMode(actual, sourcesIterable); + } + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(actual, e); + return; + } + } + + @SuppressWarnings("unchecked") + void handleIterableMode(CoreSubscriber s, + Iterable> sourcesIterable) { + Object[] scalars = new Object[8]; + Publisher[] srcs = new Publisher[8]; + + int n = 0; + int sc = 0; + + for (Publisher p : sourcesIterable) { + if (p == null) { + Operators.error(s, + Operators.onOperatorError(new NullPointerException( + "The sourcesIterable returned a null Publisher"), + s.currentContext())); + return; + } + + if (p instanceof Callable) { + Callable callable = (Callable) p; + + T v; + + try { + v = callable.call(); + } + catch (Throwable e) { + Operators.error(s, Operators.onOperatorError(e, + s.currentContext())); + return; + } + + if (v == null) { + Operators.complete(s); + return; + } + + if (n == scalars.length) { + Object[] b = new Object[n + (n >> 1)]; + System.arraycopy(scalars, 0, b, 0, n); + + Publisher[] c = new Publisher[b.length]; + //noinspection SuspiciousSystemArraycopy + System.arraycopy(srcs, 0, c, 0, n); + + scalars = b; + srcs = c; + } + + scalars[n] = v; + sc++; + } + else { + if (n == srcs.length) { + Object[] b = new Object[n + (n >> 1)]; + System.arraycopy(scalars, 0, b, 0, n); + + Publisher[] c = new Publisher[b.length]; + //noinspection SuspiciousSystemArraycopy + System.arraycopy(srcs, 0, c, 0, n); + + scalars = b; + srcs = c; + } + srcs[n] = p; + } + n++; + } + + if (n == 0) { + Operators.complete(s); + return; + } + + if (n < scalars.length) { + scalars = Arrays.copyOfRange(scalars, 0, n, scalars.getClass()); + } + + handleBoth(s, srcs, scalars, n, sc); + } + + @SuppressWarnings("unchecked") + void handleArrayMode(CoreSubscriber s, Publisher[] srcs) { + + Object[] scalars = null; //optimisation: if no scalar source, no array creation + int n = srcs.length; + + int sc = 0; + + for (int j = 0; j < n; j++) { + Publisher p = srcs[j]; + + if (p == null) { + Operators.error(s, + new NullPointerException("The sources contained a null Publisher")); + return; + } + + if (p instanceof Callable) { + Object v; + + try { + v = ((Callable) p).call(); + } + catch (Throwable e) { + Operators.error(s, Operators.onOperatorError(e, + s.currentContext())); + return; + } + + if (v == null) { + Operators.complete(s); + return; + } + + if (scalars == null) { + scalars = new Object[n]; + } + + scalars[j] = v; + sc++; + } + } + + handleBoth(s, srcs, scalars, n, sc); + } + + /** + * Handle values either from the iterable mode or array mode, taking into account the + * possibility that some sources were already resolved (being {@link Callable}): + * + * - if all sources have been resolved (sc == n), simply apply the mapper + * - if some sources have been resolved (n > sc > 0), use a coordinator with the sparse + * array, which will subscribe to unresolved sources only + * - if no source is resolved, none was callable: use a coordinator without the sparse + * array, resulting on an inner subscription for each source + * + * @param s the subscriber + * @param srcs the array of sources, some of which can be callable + * @param scalars a sparse array of values resolved for the callable sources, null if not resolved + * @param n the number of sources + * @param sc the number of already resolved sources in the scalars array + */ + void handleBoth(CoreSubscriber s, + Publisher[] srcs, + @Nullable Object[] scalars, + int n, + int sc) { + if (sc != 0 && scalars != null) { + if (n != sc) { + ZipSingleCoordinator coordinator = + new ZipSingleCoordinator<>(s, scalars, n, zipper); + + s.onSubscribe(coordinator); + + coordinator.subscribe(n, sc, srcs); + } + else { + Operators.MonoSubscriber sds = new Operators.MonoSubscriber<>(s); + + s.onSubscribe(sds); + + 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())); + return; + } + + sds.complete(r); + } + + } + else { + ZipCoordinator coordinator = + new ZipCoordinator<>(s, zipper, n, queueSupplier, prefetch); + + s.onSubscribe(coordinator); + + 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 ZipSingleCoordinator extends Operators.MonoSubscriber { + + final Function zipper; + + final Object[] scalars; + + final ZipSingleSubscriber[] subscribers; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ZipSingleCoordinator.class, "wip"); + + @SuppressWarnings("unchecked") + ZipSingleCoordinator(CoreSubscriber subscriber, + Object[] scalars, + int n, + Function zipper) { + super(subscriber); + this.zipper = zipper; + this.scalars = scalars; + ZipSingleSubscriber[] a = new ZipSingleSubscriber[n]; + for (int i = 0; i < n; i++) { + if (scalars[i] == null) { + a[i] = new ZipSingleSubscriber<>(this, i); + } + } + this.subscribers = a; + } + + void subscribe(int n, int sc, Publisher[] sources) { + WIP.lazySet(this, n - sc); + ZipSingleSubscriber[] a = subscribers; + for (int i = 0; i < n; i++) { + if (wip <= 0 || isCancelled()) { + break; + } + ZipSingleSubscriber s = a[i]; + if (s != null) { + try { + sources[i].subscribe(s); + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(s, e); + } + } + } + } + + void next(T value, int index) { + Object[] a = scalars; + a[index] = value; + if (WIP.decrementAndGet(this) == 0) { + R r; + + try { + r = Objects.requireNonNull(zipper.apply(a), + "The zipper returned a null value"); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(this, e, value, + actual.currentContext())); + return; + } + + complete(r); + } + } + + void error(Throwable e, int index) { + if (WIP.getAndSet(this, 0) > 0) { + cancelAll(); + actual.onError(e); + } + else { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + void complete(int index) { + if (WIP.getAndSet(this, 0) > 0) { + cancelAll(); + actual.onComplete(); + } + } + + @Override + public void cancel() { + super.cancel(); + cancelAll(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return wip == 0; + if (key == Attr.BUFFERED) return wip > 0 ? scalars.length : 0; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + void cancelAll() { + for (ZipSingleSubscriber s : subscribers) { + if (s != null) { + s.dispose(); + } + } + } + } + + static final class ZipSingleSubscriber + implements InnerConsumer, Disposable { + + final ZipSingleCoordinator parent; + + final int index; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(ZipSingleSubscriber.class, + Subscription.class, + "s"); + + boolean done; + + ZipSingleSubscriber(ZipSingleCoordinator parent, int index) { + this.parent = parent; + this.index = index; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + 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; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + this.s = s; + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, parent.currentContext()); + return; + } + done = true; + Operators.terminate(S, this); + parent.next(t, index); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, parent.currentContext()); + return; + } + done = true; + parent.error(t, index); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.complete(index); + } + + @Override + public void dispose() { + Operators.terminate(S, this); + } + } + + static final class ZipCoordinator + implements InnerProducer { + + final CoreSubscriber actual; + + final ZipInner[] subscribers; + + final Function zipper; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ZipCoordinator.class, "wip"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ZipCoordinator.class, "requested"); + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(ZipCoordinator.class, + Throwable.class, + "error"); + + volatile boolean cancelled; + + final Object[] current; + + ZipCoordinator(CoreSubscriber actual, + Function zipper, + int n, + Supplier> queueSupplier, + int prefetch) { + this.actual = actual; + this.zipper = zipper; + @SuppressWarnings("unchecked") ZipInner[] a = new ZipInner[n]; + for (int i = 0; i < n; i++) { + a[i] = new ZipInner<>(this, prefetch, i, queueSupplier); + } + this.current = new Object[n]; + this.subscribers = a; + } + + void subscribe(Publisher[] sources, int n) { + ZipInner[] a = subscribers; + for (int i = 0; i < n; i++) { + if (cancelled || error != null) { + return; + } + ZipInner s = a[i]; + try { + sources[i].subscribe(s); + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(s, e); + } + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + cancelAll(); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.ERROR) return error; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + void error(Throwable e, int index) { + if (Exceptions.addThrowable(ERROR, this, e)) { + drain(); + } + else { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + void cancelAll() { + for (ZipInner s : subscribers) { + s.cancel(); + } + } + + void drain() { + + if (WIP.getAndIncrement(this) != 0) { + return; + } + + final CoreSubscriber a = actual; + final ZipInner[] qs = subscribers; + final int n = qs.length; + Object[] values = current; + + int missed = 1; + + for (; ; ) { + + long r = requested; + long e = 0L; + + while (r != e) { + + if (cancelled) { + return; + } + + if (error != null) { + cancelAll(); + + Throwable ex = Exceptions.terminate(ERROR, this); + + a.onError(ex); + + return; + } + + boolean empty = false; + + for (int j = 0; j < n; j++) { + ZipInner inner = qs[j]; + if (values[j] == null) { + try { + boolean d = inner.done; + Queue q = inner.queue; + + T v = q != null ? q.poll() : null; + + boolean sourceEmpty = v == null; + if (d && sourceEmpty) { + cancelAll(); + + a.onComplete(); + return; + } + if (!sourceEmpty) { + values[j] = v; + } + else { + empty = true; + } + } + catch (Throwable ex) { + ex = Operators.onOperatorError(ex, + actual.currentContext()); + + cancelAll(); + + Exceptions.addThrowable(ERROR, this, ex); + //noinspection ConstantConditions + ex = Exceptions.terminate(ERROR, this); + + a.onError(ex); + + return; + } + } + } + + if (empty) { + break; + } + + R v; + try { + v = Objects.requireNonNull(zipper.apply(values.clone()), + "The zipper returned a null value"); + } + catch (Throwable ex) { + + ex = Operators.onOperatorError(null, ex, values.clone(), + actual.currentContext()); + cancelAll(); + + Exceptions.addThrowable(ERROR, this, ex); + //noinspection ConstantConditions + ex = Exceptions.terminate(ERROR, this); + + a.onError(ex); + + return; + } + + a.onNext(v); + + e++; + + Arrays.fill(values, null); + } + + if (r == e) { + if (cancelled) { + return; + } + + if (error != null) { + cancelAll(); + + Throwable ex = Exceptions.terminate(ERROR, this); + + a.onError(ex); + + return; + } + + for (int j = 0; j < n; j++) { + ZipInner inner = qs[j]; + if (values[j] == null) { + try { + boolean d = inner.done; + Queue q = inner.queue; + T v = q != null ? q.poll() : null; + + boolean empty = v == null; + if (d && empty) { + cancelAll(); + + a.onComplete(); + return; + } + if (!empty) { + values[j] = v; + } + } + catch (Throwable ex) { + ex = Operators.onOperatorError(null, ex, values, + actual.currentContext()); + + cancelAll(); + + Exceptions.addThrowable(ERROR, this, ex); + //noinspection ConstantConditions + ex = Exceptions.terminate(ERROR, this); + + a.onError(ex); + + return; + } + } + } + + } + + if (e != 0L) { + + for (int j = 0; j < n; j++) { + ZipInner inner = qs[j]; + inner.request(e); + } + + if (r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + } + + static final class ZipInner + implements InnerConsumer { + + final ZipCoordinator parent; + + final int prefetch; + + final int limit; + + final int index; + + final Supplier> queueSupplier; + + volatile Queue queue; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(ZipInner.class, + Subscription.class, + "s"); + + long produced; + + volatile boolean done; + + int sourceMode; + + ZipInner(ZipCoordinator parent, + int prefetch, + int index, + Supplier> queueSupplier) { + this.parent = parent; + this.prefetch = prefetch; + this.index = index; + this.queueSupplier = queueSupplier; + this.limit = Operators.unboundedOrLimit(prefetch); + } + + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + if (s instanceof Fuseable.QueueSubscription) { + Fuseable.QueueSubscription f = (Fuseable.QueueSubscription) s; + + int m = f.requestFusion(Fuseable.ANY | Fuseable.THREAD_BARRIER); + + if (m == SYNC) { + sourceMode = SYNC; + queue = f; + done = true; + parent.drain(); + return; + } + else if (m == ASYNC) { + sourceMode = ASYNC; + queue = f; + } + else { + queue = queueSupplier.get(); + } + } + else { + queue = queueSupplier.get(); + } + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if (sourceMode != ASYNC) { + if (!queue.offer(t)) { + onError(Operators.onOperatorError(s, Exceptions.failWithOverflow + (Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), currentContext())); + return; + } + } + parent.drain(); + } + + @Override + public Context currentContext() { + return parent.actual.currentContext(); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, currentContext()); + return; + } + done = true; + parent.error(t, index); + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + 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.PREFETCH) return prefetch; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + void cancel() { + Operators.terminate(S, this); + } + + void request(long n) { + if (sourceMode != SYNC) { + long p = produced + n; + if (p >= limit) { + produced = 0L; + s.request(p); + } + else { + produced = p; + } + } + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + static final class PairwiseZipper implements Function { + + final BiFunction[] zippers; + + PairwiseZipper(BiFunction[] zippers) { + this.zippers = zippers; + } + + @Override + public R apply(Object[] args) { + Object o = zippers[0].apply(args[0], args[1]); + for (int i = 1; i < zippers.length; i++) { + o = zippers[i].apply(o, args[i + 1]); + } + return (R) o; + } + + public PairwiseZipper then(BiFunction zipper) { + BiFunction[] zippers = this.zippers; + int n = zippers.length; + BiFunction[] newZippers = new BiFunction[n + 1]; + System.arraycopy(zippers, 0, newZippers, 0, n); + newZippers[n] = zipper; + + return new PairwiseZipper(newZippers); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/FluxZipIterable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/FluxZipIterable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/FluxZipIterable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.Objects; +import java.util.function.BiFunction; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Pairwise combines elements of a publisher and an iterable sequence through a function. + * + * @param the main source value type + * @param the iterable source value type + * @param the result type + * + * @see Reactive-Streams-Commons + */ +final class FluxZipIterable extends InternalFluxOperator { + + final Iterable other; + + final BiFunction zipper; + + FluxZipIterable(Flux source, + Iterable other, + BiFunction zipper) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + this.zipper = Objects.requireNonNull(zipper, "zipper"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + Iterator it = Objects.requireNonNull(other.iterator(), + "The other iterable produced a null iterator"); + + boolean b = it.hasNext(); + + if (!b) { + Operators.complete(actual); + return null; + } + + return new ZipSubscriber<>(actual, it, zipper); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ZipSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + + final Iterator it; + + final BiFunction zipper; + + Subscription s; + + boolean done; + + ZipSubscriber(CoreSubscriber actual, + Iterator it, + BiFunction zipper) { + this.actual = actual; + this.it = it; + this.zipper = zipper; + } + + @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 InnerOperator.super.scanUnsafe(key); + } + + @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; + } + + U u; + + try { + u = it.next(); + } + catch (Throwable e) { + done = true; + actual.onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + + R r; + + try { + r = Objects.requireNonNull(zipper.apply(t, u), + "The zipper returned a null value"); + } + catch (Throwable e) { + done = true; + actual.onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + + actual.onNext(r); + + boolean b; + + try { + b = it.hasNext(); + } + catch (Throwable e) { + done = true; + actual.onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + + if (!b) { + done = true; + s.cancel(); + actual.onComplete(); + } + } + + @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; + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/GroupedFlux.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/GroupedFlux.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/GroupedFlux.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015-2021 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.annotation.NonNull; + +/** + * Represents a sequence of events which has an associated key. + * + * @param the key type + * @param the value type + */ +public abstract class GroupedFlux extends Flux { + + /** + * Return the key of the {@link GroupedFlux}. + * @return the key + */ + @NonNull + public abstract K key(); +} Index: 3rdParty_sources/reactor/reactor/core/publisher/GroupedLift.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/GroupedLift.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/GroupedLift.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016-2021 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.Scannable; +import reactor.util.annotation.Nullable; + +import java.util.Objects; + +/** + * @author Simon Baslé + */ +final class GroupedLift extends GroupedFlux implements Scannable { + + final Operators.LiftFunction liftFunction; + + final GroupedFlux source; + + GroupedLift(GroupedFlux p, + Operators.LiftFunction liftFunction) { + this.source = Objects.requireNonNull(p, "source"); + this.liftFunction = liftFunction; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public K key() { + return source.key(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return source; + } + if (key == Attr.PREFETCH) { + return getPrefetch(); + } + if (key == Attr.RUN_STYLE) { + return Scannable.from(source).scanUnsafe(key); + } + if (key == Attr.LIFTER) { + return liftFunction.name; + } + + return null; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return Scannable.super.stepName(); + } + + @Override + public void subscribe(CoreSubscriber actual) { + CoreSubscriber input = + liftFunction.lifter.apply(source, actual); + + Objects.requireNonNull(input, "Lifted subscriber MUST NOT be null"); + + source.subscribe(input); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/GroupedLiftFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/GroupedLiftFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/GroupedLiftFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * @author Simon Baslé + */ +final class GroupedLiftFuseable extends GroupedFlux + implements Scannable, Fuseable { + + final Operators.LiftFunction liftFunction; + + final GroupedFlux source; + + GroupedLiftFuseable(GroupedFlux p, + Operators.LiftFunction liftFunction) { + this.source = Objects.requireNonNull(p, "source"); + this.liftFunction = liftFunction; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public K key() { + return source.key(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return source; + } + if (key == Attr.PREFETCH) { + return getPrefetch(); + } + if (key == Attr.RUN_STYLE) { + return Scannable.from(source).scanUnsafe(key); + } + if (key == Attr.LIFTER) { + return liftFunction.name; + } + + return null; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return Scannable.super.stepName(); + } + + @Override + public void subscribe(CoreSubscriber actual) { + CoreSubscriber input = + liftFunction.lifter.apply(source, actual); + + Objects.requireNonNull(input, "Lifted subscriber MUST NOT be null"); + + if (actual instanceof Fuseable.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); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/Hooks.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/Hooks.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/Hooks.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,755 @@ +/* + * Copyright (c) 2016-2021 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.*; +import java.util.concurrent.Callable; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.reactivestreams.Publisher; + +import reactor.core.Exceptions; +import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; +import reactor.core.publisher.FluxOnAssembly.MethodReturnSnapshot; +import reactor.util.Logger; +import reactor.util.Loggers; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A set of overridable lifecycle hooks that can be used for cross-cutting + * added behavior on {@link Flux}/{@link Mono} operators. + */ +public abstract class Hooks { + + /** + * Utility method to convert a {@link Publisher} to a {@link Flux} without applying {@link Hooks}. + * + * @param publisher the {@link Publisher} to convert to a {@link Flux} + * @param the type of data emitted by the {@link Publisher} + * @return the {@link Publisher} wrapped as a {@link Flux}, or the original if it was a {@link Flux} + */ + public static Flux convertToFluxBypassingHooks(Publisher publisher) { + return Flux.wrap(publisher); + } + + /** + * Utility method to convert a {@link Publisher} to a {@link Mono} without applying {@link Hooks}. + * Can optionally perform a "direct" (or unsafe) conversion when the caller is certain the {@link Publisher} + * has {@link Mono} semantics. + * + * @param publisher the {@link Publisher} to convert to a {@link Mono} + * @param enforceMonoContract {@code true} to ensure {@link Mono} semantics (by cancelling on first onNext if source isn't already a {@link Mono}), + * {@code false} to perform a direct conversion (see {@link Mono#fromDirect(Publisher)}). + * @param the type of data emitted by the {@link Publisher} + * @return the {@link Publisher} wrapped as a {@link Mono}, or the original if it was a {@link Mono} + */ + public static Mono convertToMonoBypassingHooks(Publisher publisher, boolean enforceMonoContract) { + return Mono.wrap(publisher, enforceMonoContract); + } + + + /** + * Add a {@link Publisher} operator interceptor for each operator created + * ({@link Flux} or {@link Mono}). The passed function is applied to the original + * operator {@link Publisher} and can return a different {@link Publisher}, + * on the condition that it generically maintains the same data type as the original. + * Use of the {@link Flux}/{@link Mono} APIs is discouraged as it will recursively + * call this hook, leading to {@link StackOverflowError}. + *

+ * Note that sub-hooks are cumulative, but invoking this method twice with the same instance + * (or any instance that has the same `toString`) will result in only a single instance + * being applied. See {@link #onEachOperator(String, Function)} for a variant that + * allows you to name the sub-hooks (and thus replace them or remove them individually + * later on). Can be fully reset via {@link #resetOnEachOperator()}. + *

+ * This pointcut function cannot make use of {@link Flux}, {@link Mono} or + * {@link ParallelFlux} APIs as it would lead to a recursive call to the hook: the + * operator calls would effectively invoke onEachOperator from onEachOperator. + * See {@link #convertToFluxBypassingHooks(Publisher)} and {@link #convertToMonoBypassingHooks(Publisher, boolean)}. + * + * @param onEachOperator the sub-hook: a function to intercept each operation call + * (e.g. {@code map (fn)} and {@code map(fn2)} in {@code flux.map(fn).map(fn2).subscribe()}) + * + * @see #onEachOperator(String, Function) + * @see #resetOnEachOperator(String) + * @see #resetOnEachOperator() + * @see #onLastOperator(Function) + */ + public static void onEachOperator(Function, ? extends Publisher> onEachOperator) { + onEachOperator(onEachOperator.toString(), onEachOperator); + } + + /** + * Add or replace a named {@link Publisher} operator interceptor for each operator created + * ({@link Flux} or {@link Mono}). The passed function is applied to the original + * operator {@link Publisher} and can return a different {@link Publisher}, + * on the condition that it generically maintains the same data type as the original. + * Use of the {@link Flux}/{@link Mono} APIs is discouraged as it will recursively + * call this hook, leading to {@link StackOverflowError}. + *

+ * Note that sub-hooks are cumulative. Invoking this method twice with the same key will + * replace the old sub-hook with that name, but keep the execution order (eg. A-h1, B-h2, + * A-h3 will keep A-B execution order, leading to hooks h3 then h2 being executed). + * Removing a particular key using {@link #resetOnEachOperator(String)} then adding it + * back will result in the execution order changing (the later sub-hook being executed + * last). Can be fully reset via {@link #resetOnEachOperator()}. + *

+ * This pointcut function cannot make use of {@link Flux}, {@link Mono} or + * {@link ParallelFlux} APIs as it would lead to a recursive call to the hook: the + * operator calls would effectively invoke onEachOperator from onEachOperator. + * See {@link #convertToFluxBypassingHooks(Publisher)} and {@link #convertToMonoBypassingHooks(Publisher, boolean)}. + * + * @param key the key for the sub-hook to add/replace + * @param onEachOperator the sub-hook: a function to intercept each operation call + * (e.g. {@code map (fn)} and {@code map(fn2)} in {@code flux.map(fn).map(fn2).subscribe()}) + * + * @see #onEachOperator(Function) + * @see #resetOnEachOperator(String) + * @see #resetOnEachOperator() + * @see #onLastOperator(String, Function) + */ + public static void onEachOperator(String key, Function, ? extends Publisher> onEachOperator) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(onEachOperator, "onEachOperator"); + log.debug("Hooking onEachOperator: {}", key); + + synchronized (log) { + onEachOperatorHooks.put(key, onEachOperator); + onEachOperatorHook = createOrUpdateOpHook(onEachOperatorHooks.values()); + } + } + + /** + * Remove the sub-hook with key {@code key} from the onEachOperator hook. No-op if + * no such key has been registered, and equivalent to calling {@link #resetOnEachOperator()} + * if it was the last sub-hook. + * + * @param key the key of the sub-hook to remove + */ + public static void resetOnEachOperator(String key) { + Objects.requireNonNull(key, "key"); + log.debug("Reset onEachOperator: {}", key); + + synchronized (log) { + onEachOperatorHooks.remove(key); + onEachOperatorHook = createOrUpdateOpHook(onEachOperatorHooks.values()); + } + } + + /** + * Reset global "assembly" hook tracking + */ + public static void resetOnEachOperator() { + log.debug("Reset to factory defaults : onEachOperator"); + synchronized (log) { + onEachOperatorHooks.clear(); + onEachOperatorHook = null; + } + } + + /** + * Override global error dropped strategy which by default bubble back the error. + *

+ * The hook is cumulative, so calling this method several times will set up the hook + * for as many consumer invocations (even if called with the same consumer instance). + * + * @param c the {@link Consumer} to apply to dropped errors + */ + public static void onErrorDropped(Consumer c) { + Objects.requireNonNull(c, "onErrorDroppedHook"); + log.debug("Hooking new default : onErrorDropped"); + + synchronized(log) { + if (onErrorDroppedHook != null) { + @SuppressWarnings("unchecked") Consumer _c = + ((Consumer)onErrorDroppedHook).andThen(c); + onErrorDroppedHook = _c; + } + else { + onErrorDroppedHook = c; + } + } + } + + /** + * Add a {@link Publisher} operator interceptor for the last operator created + * in every flow ({@link Flux} or {@link Mono}). The passed function is applied + * to the original operator {@link Publisher} and can return a different {@link Publisher}, + * on the condition that it generically maintains the same data type as the original. + *

+ * Note that sub-hooks are cumulative, but invoking this method twice with the same + * instance (or any instance that has the same `toString`) will result in only a single + * instance being applied. See {@link #onLastOperator(String, Function)} for a variant + * that allows you to name the sub-hooks (and thus replace them or remove them individually + * later on). Can be fully reset via {@link #resetOnLastOperator()}. + *

+ * This pointcut function cannot make use of {@link Flux}, {@link Mono} or + * {@link ParallelFlux} APIs as it would lead to a recursive call to the hook: the + * operator calls would effectively invoke onEachOperator from onEachOperator. + * See {@link #convertToFluxBypassingHooks(Publisher)} and {@link #convertToMonoBypassingHooks(Publisher, boolean)}. + * + * @param onLastOperator the sub-hook: a function to intercept last operation call + * (e.g. {@code map(fn2)} in {@code flux.map(fn).map(fn2).subscribe()}) + * + * @see #onLastOperator(String, Function) + * @see #resetOnLastOperator(String) + * @see #resetOnLastOperator() + * @see #onEachOperator(Function) + */ + public static void onLastOperator(Function, ? extends Publisher> onLastOperator) { + onLastOperator(onLastOperator.toString(), onLastOperator); + } + + /** + * Add or replace a named {@link Publisher} operator interceptor for the last operator created + * in every flow ({@link Flux} or {@link Mono}). The passed function is applied + * to the original operator {@link Publisher} and can return a different {@link Publisher}, + * on the condition that it generically maintains the same data type as the original. + * Use of the {@link Flux}/{@link Mono} APIs is discouraged as it will recursively + * call this hook, leading to {@link StackOverflowError}. + *

+ * Note that sub-hooks are cumulative. Invoking this method twice with the same key will + * replace the old sub-hook with that name, but keep the execution order (eg. A-h1, B-h2, + * A-h3 will keep A-B execution order, leading to hooks h3 then h2 being executed). + * Removing a particular key using {@link #resetOnLastOperator(String)} then adding it + * back will result in the execution order changing (the later sub-hook being executed + * last). Can be fully reset via {@link #resetOnLastOperator()}. + *

+ * This pointcut function cannot make use of {@link Flux}, {@link Mono} or + * {@link ParallelFlux} APIs as it would lead to a recursive call to the hook: the + * operator calls would effectively invoke onEachOperator from onEachOperator. + * See {@link #convertToFluxBypassingHooks(Publisher)} and {@link #convertToMonoBypassingHooks(Publisher, boolean)}. + * + * @param key the key for the sub-hook to add/replace + * @param onLastOperator the sub-hook: a function to intercept last operation call + * (e.g. {@code map(fn2)} in {@code flux.map(fn).map(fn2).subscribe()}) + * + * @see #onLastOperator(Function) + * @see #resetOnLastOperator(String) + * @see #resetOnLastOperator() + * @see #onEachOperator(String, Function) + */ + public static void onLastOperator(String key, Function, ? extends Publisher> onLastOperator) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(onLastOperator, "onLastOperator"); + log.debug("Hooking onLastOperator: {}", key); + + synchronized (log) { + onLastOperatorHooks.put(key, onLastOperator); + onLastOperatorHook = createOrUpdateOpHook(onLastOperatorHooks.values()); + } + } + + /** + * Remove the sub-hook with key {@code key} from the onLastOperator hook. No-op if + * no such key has been registered, and equivalent to calling {@link #resetOnLastOperator()} + * if it was the last sub-hook. + * + * @param key the key of the sub-hook to remove + */ + public static void resetOnLastOperator(String key) { + Objects.requireNonNull(key, "key"); + log.debug("Reset onLastOperator: {}", key); + + synchronized (log) { + onLastOperatorHooks.remove(key); + onLastOperatorHook = createOrUpdateOpHook(onLastOperatorHooks.values()); + } + } + + /** + * Reset global "subscriber" hook tracking + */ + public static void resetOnLastOperator() { + log.debug("Reset to factory defaults : onLastOperator"); + synchronized (log) { + onLastOperatorHooks.clear(); + onLastOperatorHook = null; + } + } + + /** + * Override global data dropped strategy which by default logs at DEBUG level. + *

+ * The hook is cumulative, so calling this method several times will set up the hook + * for as many consumer invocations (even if called with the same consumer instance). + * + * @param c the {@link Consumer} to apply to data (onNext) that is dropped + * @see #onNextDroppedFail() + */ + public static void onNextDropped(Consumer c) { + Objects.requireNonNull(c, "onNextDroppedHook"); + log.debug("Hooking new default : onNextDropped"); + + synchronized(log) { + if (onNextDroppedHook != null) { + onNextDroppedHook = onNextDroppedHook.andThen(c); + } + else { + onNextDroppedHook = c; + } + } + } + + /** + * Resets {@link #resetOnNextDropped() onNextDropped hook(s)} and + * apply a strategy of throwing {@link Exceptions#failWithCancel()} instead. + *

+ * Use {@link #resetOnNextDropped()} to reset to the default strategy of logging. + */ + public static void onNextDroppedFail() { + log.debug("Enabling failure mode for onNextDropped"); + + synchronized(log) { + onNextDroppedHook = n -> {throw Exceptions.failWithCancel();}; + } + } + + /** + * Enable operator stack recorder that captures a declaration stack whenever an + * operator is instantiated. When errors are observed later on, they will be + * enriched with a Suppressed Exception detailing the original assembly line stack. + * Must be called before producers (e.g. Flux.map, Mono.fromCallable) are actually + * called to intercept the right stack information. + *

+ * This is added as a specifically-keyed sub-hook in {@link #onEachOperator(String, Function)}. + */ + public static void onOperatorDebug() { + log.debug("Enabling stacktrace debugging via onOperatorDebug"); + GLOBAL_TRACE = true; + } + + /** + * Reset global operator debug. + */ + public static void resetOnOperatorDebug() { + GLOBAL_TRACE = false; + } + + /** + * Set the custom global error mode hook for operators that support resuming + * during an error in their {@link org.reactivestreams.Subscriber#onNext(Object)}. + *

+ * The hook is a {@link BiFunction} of {@link Throwable} and potentially null {@link Object}. + * If it is also a {@link java.util.function.BiPredicate}, its + * {@link java.util.function.BiPredicate#test(Object, Object) test} method should be + * used to determine if an error should be processed (matching predicate) or completely + * skipped (non-matching predicate). Typical usage, as in {@link Operators}, is to + * check if the predicate matches and fallback to {@link Operators#onOperatorError(Throwable, Context)} + * if it doesn't. + * + * @param onNextError the new {@link BiFunction} to use. + */ + public static void onNextError(BiFunction onNextError) { + Objects.requireNonNull(onNextError, "onNextError"); + log.debug("Hooking new default : onNextError"); + + if (onNextError instanceof OnNextFailureStrategy) { + synchronized(log) { + onNextErrorHook = (OnNextFailureStrategy) onNextError; + } + } + else { + synchronized(log) { + onNextErrorHook = new OnNextFailureStrategy.LambdaOnNextErrorStrategy(onNextError); + } + } + } + + /** + * Add a custom error mapping, overriding the default one. Custom mapping can be an + * accumulation of several sub-hooks each subsequently added via this method. + *

+ * Note that sub-hooks are cumulative, but invoking this method twice with the same + * instance (or any instance that has the same `toString`) will result in only a single + * instance being applied. See {@link #onOperatorError(String, BiFunction)} for a variant + * that allows you to name the sub-hooks (and thus replace them or remove them individually + * later on). Can be fully reset via {@link #resetOnOperatorError()}. + *

+ * For reference, the default mapping is to unwrap the exception and, if the second + * parameter is another exception, to add it to the first as suppressed. + * + * @param onOperatorError an operator error {@link BiFunction} mapper, returning an arbitrary exception + * given the failure and optionally some original context (data or error). + * + * @see #onOperatorError(String, BiFunction) + * @see #resetOnOperatorError(String) + * @see #resetOnOperatorError() + */ + public static void onOperatorError(BiFunction onOperatorError) { + onOperatorError(onOperatorError.toString(), onOperatorError); + } + + /** + * Add or replace a named custom error mapping, overriding the default one. Custom + * mapping can be an accumulation of several sub-hooks each subsequently added via this + * method. + *

+ * Note that invoking this method twice with the same key will replace the old sub-hook + * with that name, but keep the execution order (eg. A-h1, B-h2, A-h3 will keep A-B + * execution order, leading to hooks h3 then h2 being executed). Removing a particular + * key using {@link #resetOnOperatorError(String)} then adding it back will result in + * the execution order changing (the later sub-hook being executed last). + * Can be fully reset via {@link #resetOnOperatorError()}. + *

+ * For reference, the default mapping is to unwrap the exception and, if the second + * parameter is another exception, to add it to the first as a suppressed. + * + * @param key the key for the sub-hook to add/replace + * @param onOperatorError an operator error {@link BiFunction} mapper, returning an arbitrary exception + * given the failure and optionally some original context (data or error). + * + * @see #onOperatorError(String, BiFunction) + * @see #resetOnOperatorError(String) + * @see #resetOnOperatorError() + */ + public static void onOperatorError(String key, BiFunction onOperatorError) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(onOperatorError, "onOperatorError"); + log.debug("Hooking onOperatorError: {}", key); + synchronized (log) { + onOperatorErrorHooks.put(key, onOperatorError); + onOperatorErrorHook = createOrUpdateOpErrorHook(onOperatorErrorHooks.values()); + } + } + + /** + * Remove the sub-hook with key {@code key} from the onOperatorError hook. No-op if + * no such key has been registered, and equivalent to calling {@link #resetOnOperatorError()} + * if it was the last sub-hook. + * + * @param key the key of the sub-hook to remove + */ + public static void resetOnOperatorError(String key) { + Objects.requireNonNull(key, "key"); + log.debug("Reset onOperatorError: {}", key); + synchronized (log) { + onOperatorErrorHooks.remove(key); + onOperatorErrorHook = createOrUpdateOpErrorHook(onOperatorErrorHooks.values()); + } + } + + /** + * Reset global operator error mapping to the default behavior. + *

+ * For reference, the default mapping is to unwrap the exception and, if the second + * parameter is another exception, to add it to the first as a suppressed. + */ + public static void resetOnOperatorError() { + log.debug("Reset to factory defaults : onOperatorError"); + synchronized (log) { + onOperatorErrorHooks.clear(); + onOperatorErrorHook = null; + } + } + + /** + * Reset global error dropped strategy to bubbling back the error. + */ + public static void resetOnErrorDropped() { + log.debug("Reset to factory defaults : onErrorDropped"); + synchronized (log) { + onErrorDroppedHook = null; + } + } + + /** + * Reset global data dropped strategy to throwing via {@link + * reactor.core.Exceptions#failWithCancel()} + */ + public static void resetOnNextDropped() { + log.debug("Reset to factory defaults : onNextDropped"); + synchronized (log) { + onNextDroppedHook = null; + } + } + + /** + * Reset global onNext error handling strategy to terminating the sequence with + * an onError and cancelling upstream ({@link OnNextFailureStrategy#STOP}). + */ + public static void resetOnNextError() { + log.debug("Reset to factory defaults : onNextError"); + synchronized (log) { + onNextErrorHook = null; + } + } + + /** + * Globally enables the {@link Context} loss detection in operators like + * {@link Flux#transform} or {@link Mono#transformDeferred} when non-Reactor types are used. + * + * An exception will be thrown upon applying the transformation if the original {@link Context} isn't reachable + * (ie. it has been replaced by a totally different {@link Context}, or no {@link Context} at all) + */ + public static void enableContextLossTracking() { + DETECT_CONTEXT_LOSS = true; + } + + /** + * Globally disables the {@link Context} loss detection that was previously + * enabled by {@link #enableContextLossTracking()}. + * + */ + public static void disableContextLossTracking() { + DETECT_CONTEXT_LOSS = false; + } + + @Nullable + @SuppressWarnings("unchecked") + static Function createOrUpdateOpHook(Collection, ? extends Publisher>> hooks) { + Function composite = null; + for (Function, ? extends Publisher> function : hooks) { + Function op = (Function) function; + if (composite != null) { + composite = composite.andThen(op); + } + else { + composite = (Function) op; + } + } + return composite; + } + + @Nullable + static BiFunction createOrUpdateOpErrorHook(Collection> hooks) { + BiFunction composite = null; + for (BiFunction function : hooks) { + if (composite != null) { + BiFunction ff = composite; + composite = (e, data) -> function.apply(ff.apply(e, data), data); + } + else { + composite = function; + } + } + return composite; + } + + //Hooks that are transformative + static Function onEachOperatorHook; + static volatile Function onLastOperatorHook; + static volatile BiFunction onOperatorErrorHook; + + //Hooks that are just callbacks + static volatile Consumer onErrorDroppedHook; + static volatile Consumer onNextDroppedHook; + + //Special hook that is between the two (strategy can be transformative, but not named) + static volatile OnNextFailureStrategy onNextErrorHook; + + + //For transformative hooks, allow to name them, keep track in an internal Map that retains insertion order + //internal use only as it relies on external synchronization + private static final LinkedHashMap, ? extends Publisher>> onEachOperatorHooks; + private static final LinkedHashMap, ? extends Publisher>> onLastOperatorHooks; + private static final LinkedHashMap> onOperatorErrorHooks; + + private static final LinkedHashMap, Queue>> QUEUE_WRAPPERS = new LinkedHashMap<>(1); + + private static Function, Queue> QUEUE_WRAPPER = Function.identity(); + + //Immutable views on hook trackers, for testing purpose + static final Map, ? extends Publisher>> getOnEachOperatorHooks() { + return Collections.unmodifiableMap(onEachOperatorHooks); + } + static final Map, ? extends Publisher>> getOnLastOperatorHooks() { + return Collections.unmodifiableMap(onLastOperatorHooks); + } + static final Map> getOnOperatorErrorHooks() { + return Collections.unmodifiableMap(onOperatorErrorHooks); + } + + static final Logger log = Loggers.getLogger(Hooks.class); + + /** + * A key that can be used to store a sequence-specific {@link Hooks#onErrorDropped(Consumer)} + * hook in a {@link Context}, as a {@link Consumer Consumer<Throwable>}. + */ + static final String KEY_ON_ERROR_DROPPED = "reactor.onErrorDropped.local"; + /** + * A key that can be used to store a sequence-specific {@link Hooks#onNextDropped(Consumer)} + * hook in a {@link Context}, as a {@link Consumer Consumer<Object>}. + */ + static final String KEY_ON_NEXT_DROPPED = "reactor.onNextDropped.local"; + /** + * A key that can be used to store a sequence-specific {@link Hooks#onOperatorError(BiFunction)} + * hook in a {@link Context}, as a {@link BiFunction BiFunction<Throwable, Object, Throwable>}. + */ + static final String KEY_ON_OPERATOR_ERROR = "reactor.onOperatorError.local"; + + /** + * A key that can be used to store a sequence-specific onDiscard(Consumer) + * hook in a {@link Context}, as a {@link Consumer Consumer<Object>}. + */ + static final String KEY_ON_DISCARD = "reactor.onDiscard.local"; + + /** + * A key that can be used to store a sequence-specific {@link Hooks#onOperatorError(BiFunction)} + * hook THAT IS ONLY APPLIED TO Operators{@link Operators#onRejectedExecution(Throwable, Context) onRejectedExecution} + * in a {@link Context}, as a {@link BiFunction BiFunction<Throwable, Object, Throwable>}. + */ + static final String KEY_ON_REJECTED_EXECUTION = "reactor.onRejectedExecution.local"; + + static boolean GLOBAL_TRACE = initStaticGlobalTrace(); + + + static boolean DETECT_CONTEXT_LOSS = false; + + static { + onEachOperatorHooks = new LinkedHashMap<>(1); + onLastOperatorHooks = new LinkedHashMap<>(1); + onOperatorErrorHooks = new LinkedHashMap<>(1); + } + + //isolated on static method for testing purpose + static boolean initStaticGlobalTrace() { + return Boolean.parseBoolean(System.getProperty("reactor.trace.operatorStacktrace", + "false")); + } + + Hooks() { + } + + /** + * + * @deprecated Should only be used by the instrumentation, DOES NOT guarantee any compatibility + */ + @Nullable + @Deprecated + public static > Publisher addReturnInfo(@Nullable P publisher, String method) { + if (publisher == null) { + return null; + } + return addAssemblyInfo(publisher, new MethodReturnSnapshot(method)); + } + + /** + * + * @deprecated Should only be used by the instrumentation, DOES NOT guarantee any compatibility + */ + @Nullable + @Deprecated + public static > Publisher addCallSiteInfo(@Nullable P publisher, String callSite) { + if (publisher == null) { + return null; + } + return addAssemblyInfo(publisher, new AssemblySnapshot(callSite)); + } + + static > Publisher addAssemblyInfo(P publisher, AssemblySnapshot stacktrace) { + if (publisher instanceof Callable) { + if (publisher instanceof Mono) { + return new MonoCallableOnAssembly<>((Mono) publisher, stacktrace); + } + return new FluxCallableOnAssembly<>((Flux) publisher, stacktrace); + } + if (publisher instanceof Mono) { + return new MonoOnAssembly<>((Mono) publisher, stacktrace); + } + if (publisher instanceof ParallelFlux) { + return new ParallelFluxOnAssembly<>((ParallelFlux) publisher, stacktrace); + } + if (publisher instanceof ConnectableFlux) { + return new ConnectableFluxOnAssembly<>((ConnectableFlux) publisher, stacktrace); + } + return new FluxOnAssembly<>((Flux) publisher, stacktrace); + } + + /** + * Adds a wrapper for every {@link Queue} used in Reactor. + * Note that it won't affect existing instances of {@link Queue}. + * + * Hint: one may use {@link AbstractQueue} to reduce the number of methods to implement. + * + * @implNote the resulting {@link Queue} MUST NOT change {@link Queue}'s behavior. Only side effects are allowed. + * + * @see #removeQueueWrapper(String) + */ + public static void addQueueWrapper(String key, Function, Queue> decorator) { + synchronized (QUEUE_WRAPPERS) { + QUEUE_WRAPPERS.put(key, decorator); + Function, Queue> newHook = null; + for (Function, Queue> function : QUEUE_WRAPPERS.values()) { + if (newHook == null) { + newHook = function; + } + else { + newHook = newHook.andThen(function); + } + } + QUEUE_WRAPPER = newHook; + } + } + + /** + * Removes existing {@link Queue} wrapper by key. + * + * @see #addQueueWrapper(String, Function) + */ + public static void removeQueueWrapper(String key) { + synchronized (QUEUE_WRAPPERS) { + QUEUE_WRAPPERS.remove(key); + if (QUEUE_WRAPPERS.isEmpty()) { + QUEUE_WRAPPER = Function.identity(); + } + else { + Function, Queue> newHook = null; + for (Function, Queue> function : QUEUE_WRAPPERS.values()) { + if (newHook == null) { + newHook = function; + } + else { + newHook = newHook.andThen(function); + } + } + QUEUE_WRAPPER = newHook; + } + } + } + + /** + * Remove all queue wrappers. + * + * @see #addQueueWrapper(String, Function) + * @see #removeQueueWrapper(String) + */ + public static void removeQueueWrappers() { + synchronized (QUEUE_WRAPPERS) { + QUEUE_WRAPPERS.clear(); + QUEUE_WRAPPER = Function.identity(); + } + } + + /** + * Applies the {@link Queue} wrappers that were previously registered. + * SHOULD NOT change the behavior of the provided {@link Queue}. + * + * @param queue the {@link Queue} to wrap. + * @return the result of applying {@link Queue} wrappers registered with {@link #addQueueWrapper(String, Function)}. + * + * @see #addQueueWrapper(String, Function) + * @see #removeQueueWrapper(String) + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static Queue wrapQueue(Queue queue) { + return (Queue) QUEUE_WRAPPER.apply(queue); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ImmutableSignal.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ImmutableSignal.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ImmutableSignal.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2016-2021 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.io.Serializable; +import java.util.Objects; + +import org.reactivestreams.Subscription; + +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +/** + * The common implementation of a {@link Signal} (serializable and immutable). + * Use Signal factory methods to create an instance. + *

+ * Associated {@link Context} are not serialized. + * + * @author Stephane Maldini + * @author Simon Baslé + */ +final class ImmutableSignal implements Signal, Serializable { + + private static final long serialVersionUID = -2004454746525418508L; + + private final transient ContextView contextView; + + private final SignalType type; + private final Throwable throwable; + + private final T value; + + private transient final Subscription subscription; + + ImmutableSignal(ContextView contextView, SignalType type, @Nullable T value, @Nullable Throwable e, @Nullable Subscription subscription) { + this.contextView = contextView; + this.value = value; + this.subscription = subscription; + this.throwable = e; + this.type = type; + } + + @Override + @Nullable + public Throwable getThrowable() { + return throwable; + } + + @Override + @Nullable + public Subscription getSubscription() { + return subscription; + } + + @Override + @Nullable + public T get() { + return value; + } + + @Override + public SignalType getType() { + return type; + } + + @Override + public ContextView getContextView() { + return contextView; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof Signal)) { + return false; + } + + Signal signal = (Signal) o; + + if (getType() != signal.getType()) { + return false; + } + if (isOnComplete()) { + return true; + } + if (isOnSubscribe()) { + return Objects.equals(this.getSubscription(), signal.getSubscription()); + } + if (isOnError()) { + return Objects.equals(this.getThrowable(), signal.getThrowable()); + } + if (isOnNext()) { + return Objects.equals(this.get(), signal.get()); + } + return false; + } + + @Override + public int hashCode() { + int result = getType().hashCode(); + if (isOnError()) { + return 31 * result + (getThrowable() != null ? getThrowable().hashCode() : + 0); + } + if (isOnNext()) { + //noinspection ConstantConditions + return 31 * result + (get() != null ? get().hashCode() : 0); + } + if (isOnSubscribe()) { + return 31 * result + (getSubscription() != null ? + getSubscription().hashCode() : 0); + } + return result; + } + + @Override + public String toString() { + switch (this.getType()) { + case ON_SUBSCRIBE: + return String.format("onSubscribe(%s)", this.getSubscription()); + case ON_NEXT: + return String.format("onNext(%s)", this.get()); + case ON_ERROR: + return String.format("onError(%s)", this.getThrowable()); + case ON_COMPLETE: + return "onComplete()"; + default: + return String.format("Signal type=%s", this.getType()); + } + } + + private static final Signal ON_COMPLETE = + new ImmutableSignal<>(Context.empty(), SignalType.ON_COMPLETE, null, null, null); + + /** + * @return a singleton signal to signify onComplete. + * As Signal is now associated with {@link Context}, prefer using per-subscription instances. + * This instance is used when context doesn't matter. + */ + @SuppressWarnings("unchecked") + static Signal onComplete() { + return (Signal) ON_COMPLETE; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/InnerConsumer.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/InnerConsumer.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/InnerConsumer.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016-2021 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.Scannable; + +/** + * + * A {@link InnerConsumer} is a {@link Scannable} {@link CoreSubscriber}. + * + * @param input operator produced type + * + * @author Stephane Maldini + */ +interface InnerConsumer + extends CoreSubscriber, Scannable { + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/InnerOperator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/InnerOperator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/InnerOperator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015-2021 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.context.Context; + +/** + * + * @param input operator consumed type + * @param output operator produced type + * + * @author Stephane Maldini + */ +interface InnerOperator + extends InnerConsumer, InnerProducer { + + @Override + default Context currentContext() { + return actual().currentContext(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/InnerProducer.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/InnerProducer.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/InnerProducer.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016-2021 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.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * + * {@link InnerProducer} is a {@link reactor.core.Scannable} {@link Subscription} that produces + * data to an {@link #actual()} {@link Subscriber} + * + * @param output operator produced type + * + * @author Stephane Maldini + */ +interface InnerProducer + extends Scannable, Subscription { + + CoreSubscriber actual(); + + @Override + @Nullable + default Object scanUnsafe(Attr key){ + if (key == Attr.ACTUAL) { + return actual(); + } + return null; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/InternalConnectableFluxOperator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/InternalConnectableFluxOperator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/InternalConnectableFluxOperator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2016-2021 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.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +abstract class InternalConnectableFluxOperator extends ConnectableFlux implements Scannable, + OptimizableOperator { + + final ConnectableFlux source; + + @Nullable + final OptimizableOperator optimizableOperator; + + /** + * Build an {@link InternalConnectableFluxOperator} wrapper around the passed parent {@link ConnectableFlux} + * + * @param source the {@link ConnectableFlux} to decorate + */ + public InternalConnectableFluxOperator(ConnectableFlux source) { + this.source = source; + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + @Override + @SuppressWarnings("unchecked") + public final void subscribe(CoreSubscriber subscriber) { + OptimizableOperator operator = this; + try { + while (true) { + subscriber = operator.subscribeOrReturn(subscriber); + if (subscriber == null) { + // null means "I will subscribe myself", returning... + return; + } + OptimizableOperator newSource = operator.nextOptimizableSource(); + if (newSource == null) { + operator.source().subscribe(subscriber); + return; + } + operator = newSource; + } + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(subscriber, e); + return; + } + } + + @Override + @Nullable + public abstract CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable; + + @Override + public final CorePublisher source() { + return source; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + @Nullable + public Object scanUnsafe(Scannable.Attr key) { + if (key == Scannable.Attr.PREFETCH) return getPrefetch(); + if (key == Scannable.Attr.PARENT) return source; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/InternalEmptySink.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/InternalEmptySink.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/InternalEmptySink.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020-2021 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.publisher.Sinks.EmissionException; + +interface InternalEmptySink extends Sinks.Empty, ContextHolder { + + @Override + default void emitEmpty(Sinks.EmitFailureHandler failureHandler) { + for (;;) { + Sinks.EmitResult emitResult = tryEmitEmpty(); + if (emitResult.isSuccess()) { + return; + } + + boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_COMPLETE, + emitResult); + if (shouldRetry) { + continue; + } + + switch (emitResult) { + case FAIL_ZERO_SUBSCRIBER: + case FAIL_OVERFLOW: + case FAIL_CANCELLED: + case FAIL_TERMINATED: + return; + case FAIL_NON_SERIALIZED: + throw new EmissionException(emitResult, + "Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially." + ); + default: + throw new EmissionException(emitResult, "Unknown emitResult value"); + } + } + } + + @Override + default void emitError(Throwable error, Sinks.EmitFailureHandler failureHandler) { + for (;;) { + Sinks.EmitResult emitResult = tryEmitError(error); + if (emitResult.isSuccess()) { + return; + } + + boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_ERROR, + emitResult); + if (shouldRetry) { + continue; + } + + switch (emitResult) { + case FAIL_ZERO_SUBSCRIBER: + case FAIL_OVERFLOW: + case FAIL_CANCELLED: + return; + case FAIL_TERMINATED: + Operators.onErrorDropped(error, currentContext()); + return; + case FAIL_NON_SERIALIZED: + throw new EmissionException(emitResult, + "Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially." + ); + default: + throw new EmissionException(emitResult, "Unknown emitResult value"); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/InternalFluxOperator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/InternalFluxOperator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/InternalFluxOperator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2016-2021 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.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +abstract class InternalFluxOperator extends FluxOperator implements Scannable, + OptimizableOperator { + + @Nullable + final OptimizableOperator optimizableOperator; + + /** + * Build a {@link InternalFluxOperator} wrapper around the passed parent {@link Publisher} + * + * @param source the {@link Publisher} to decorate + */ + protected InternalFluxOperator(Flux source) { + super(source); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + @Override + @SuppressWarnings("unchecked") + public final void subscribe(CoreSubscriber subscriber) { + OptimizableOperator operator = this; + try { + while (true) { + subscriber = operator.subscribeOrReturn(subscriber); + if (subscriber == null) { + // null means "I will subscribe myself", returning... + return; + } + OptimizableOperator newSource = operator.nextOptimizableSource(); + if (newSource == null) { + operator.source().subscribe(subscriber); + return; + } + operator = newSource; + } + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(subscriber, e); + return; + } + } + + @Nullable + public abstract CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable; + + @Override + public final CorePublisher source() { + return source; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.PARENT) return source; + return null; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/InternalManySink.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/InternalManySink.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/InternalManySink.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020-2021 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.Exceptions; +import reactor.core.publisher.Sinks.EmissionException; + +interface InternalManySink extends Sinks.Many, ContextHolder { + + @Override + default void emitNext(T value, Sinks.EmitFailureHandler failureHandler) { + for (;;) { + Sinks.EmitResult emitResult = tryEmitNext(value); + if (emitResult.isSuccess()) { + return; + } + + boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_NEXT, + emitResult); + if (shouldRetry) { + continue; + } + + switch (emitResult) { + case FAIL_ZERO_SUBSCRIBER: + //we want to "discard" without rendering the sink terminated. + // effectively NO-OP cause there's no subscriber, so no context :( + return; + case FAIL_OVERFLOW: + Operators.onDiscard(value, currentContext()); + //the emitError will onErrorDropped if already terminated + emitError(Exceptions.failWithOverflow("Backpressure overflow during Sinks.Many#emitNext"), + failureHandler); + return; + case FAIL_CANCELLED: + Operators.onDiscard(value, currentContext()); + return; + case FAIL_TERMINATED: + Operators.onNextDropped(value, currentContext()); + return; + case FAIL_NON_SERIALIZED: + throw new EmissionException(emitResult, + "Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially." + ); + default: + throw new EmissionException(emitResult, "Unknown emitResult value"); + } + } + } + + @Override + default void emitComplete(Sinks.EmitFailureHandler failureHandler) { + for (;;) { + Sinks.EmitResult emitResult = tryEmitComplete(); + if (emitResult.isSuccess()) { + return; + } + + boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_COMPLETE, + emitResult); + if (shouldRetry) { + continue; + } + + switch (emitResult) { + case FAIL_ZERO_SUBSCRIBER: + case FAIL_OVERFLOW: + case FAIL_CANCELLED: + case FAIL_TERMINATED: + return; + case FAIL_NON_SERIALIZED: + throw new EmissionException(emitResult, + "Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially." + ); + default: + throw new EmissionException(emitResult, "Unknown emitResult value"); + } + } + } + + @Override + default void emitError(Throwable error, Sinks.EmitFailureHandler failureHandler) { + for (;;) { + Sinks.EmitResult emitResult = tryEmitError(error); + if (emitResult.isSuccess()) { + return; + } + + boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_ERROR, + emitResult); + if (shouldRetry) { + continue; + } + + switch (emitResult) { + case FAIL_ZERO_SUBSCRIBER: + case FAIL_OVERFLOW: + case FAIL_CANCELLED: + return; + case FAIL_TERMINATED: + Operators.onErrorDropped(error, currentContext()); + return; + case FAIL_NON_SERIALIZED: + throw new EmissionException(emitResult, + "Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially." + ); + default: + throw new EmissionException(emitResult, "Unknown emitResult value"); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/InternalMonoOperator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/InternalMonoOperator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/InternalMonoOperator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016-2021 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.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * 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}. + * + * @param delegate {@link Publisher} type + * @param produced type + */ +abstract class InternalMonoOperator extends MonoOperator implements Scannable, + OptimizableOperator { + + @Nullable + final OptimizableOperator optimizableOperator; + + protected InternalMonoOperator(Mono source) { + super(source); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + @Override + @SuppressWarnings("unchecked") + public final void subscribe(CoreSubscriber subscriber) { + OptimizableOperator operator = this; + try { + while (true) { + subscriber = operator.subscribeOrReturn(subscriber); + if (subscriber == null) { + // null means "I will subscribe myself", returning... + return; + } + OptimizableOperator newSource = operator.nextOptimizableSource(); + if (newSource == null) { + operator.source().subscribe(subscriber); + return; + } + operator = newSource; + } + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(subscriber, e); + return; + } + } + + @Nullable + public abstract CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable; + + @Override + public final CorePublisher source() { + return source; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/InternalOneSink.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/InternalOneSink.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/InternalOneSink.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020-2021 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.Exceptions; +import reactor.core.publisher.Sinks.EmissionException; +import reactor.util.annotation.Nullable; + +interface InternalOneSink extends Sinks.One, InternalEmptySink { + + @Override + default void emitValue(@Nullable T value, Sinks.EmitFailureHandler failureHandler) { + if (value == null) { + emitEmpty(failureHandler); + return; + } + + for (;;) { + Sinks.EmitResult emitResult = tryEmitValue(value); + if (emitResult.isSuccess()) { + return; + } + + boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_NEXT, + emitResult); + if (shouldRetry) { + continue; + } + + switch (emitResult) { + case FAIL_ZERO_SUBSCRIBER: + //we want to "discard" without rendering the sink terminated. + // effectively NO-OP cause there's no subscriber, so no context :( + return; + case FAIL_OVERFLOW: + Operators.onDiscard(value, currentContext()); + //the emitError will onErrorDropped if already terminated + emitError(Exceptions.failWithOverflow("Backpressure overflow during Sinks.One#emitValue"), + failureHandler); + return; + case FAIL_CANCELLED: + Operators.onDiscard(value, currentContext()); + return; + case FAIL_TERMINATED: + Operators.onNextDropped(value, currentContext()); + return; + case FAIL_NON_SERIALIZED: + throw new EmissionException(emitResult, + "Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially." + ); + default: + throw new EmissionException(emitResult, "Unknown emitResult value"); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/LambdaMonoSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/LambdaMonoSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/LambdaMonoSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2016-2021 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.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.Disposable; +import reactor.core.Exceptions; +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; + +/** + * An unbounded Java Lambda adapter to {@link Subscriber}, targeted at {@link Mono}. + * + * @param the value type + */ +final class LambdaMonoSubscriber implements InnerConsumer, Disposable { + + final Consumer consumer; + final Consumer errorConsumer; + final Runnable completeConsumer; + final Consumer subscriptionConsumer; + final Context initialContext; + + volatile Subscription subscription; + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(LambdaMonoSubscriber.class, + Subscription.class, + "subscription"); + + /** + * Create a {@link Subscriber} reacting onNext, onError and onComplete. The subscriber + * will automatically request Long.MAX_VALUE onSubscribe. + *

+ * The argument {@code subscriptionHandler} is executed once by new subscriber to + * generate a context shared by every request calls. + * + * @param consumer A {@link Consumer} with argument onNext data + * @param errorConsumer A {@link Consumer} called onError + * @param completeConsumer A {@link Runnable} called onComplete with the actual + * context if any + * @param subscriptionConsumer A {@link Consumer} called with the {@link Subscription} + * to perform initial request, or null to request max + * @param initialContext A {@link Context} for this subscriber, or null to use the default + * of an {@link Context#empty() empty Context}. + */ + LambdaMonoSubscriber(@Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer, + @Nullable Consumer subscriptionConsumer, + @Nullable Context initialContext) { + this.consumer = consumer; + this.errorConsumer = errorConsumer; + this.completeConsumer = completeConsumer; + this.subscriptionConsumer = subscriptionConsumer; + this.initialContext = initialContext == null ? Context.empty() : initialContext; + } + + /** + * Create a {@link Subscriber} reacting onNext, onError and onComplete. The subscriber + * will automatically request Long.MAX_VALUE onSubscribe. + *

+ * The argument {@code subscriptionHandler} is executed once by new subscriber to + * generate a context shared by every request calls. + * + * @param consumer A {@link Consumer} with argument onNext data + * @param errorConsumer A {@link Consumer} called onError + * @param completeConsumer A {@link Runnable} called onComplete with the actual + * context if any + * @param subscriptionConsumer A {@link Consumer} called with the {@link Subscription} + * to perform initial request, or null to request max + */ //left mainly for the benefit of tests + LambdaMonoSubscriber(@Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer, + @Nullable Consumer subscriptionConsumer) { + this(consumer, errorConsumer, completeConsumer, subscriptionConsumer, null); + } + + @Override + public Context currentContext() { + return this.initialContext; + } + + @Override + public final void onSubscribe(Subscription s) { + if (Operators.validate(subscription, s)) { + this.subscription = s; + + if (subscriptionConsumer != null) { + try { + subscriptionConsumer.accept(s); + } + catch (Throwable t) { + Exceptions.throwIfFatal(t); + s.cancel(); + onError(t); + } + } + else { + s.request(Long.MAX_VALUE); + } + + } + } + + @Override + public final void onComplete() { + Subscription s = S.getAndSet(this, Operators.cancelledSubscription()); + if (s == Operators.cancelledSubscription()) { + return; + } + if (completeConsumer != null) { + try { + completeConsumer.run(); + } + catch (Throwable t) { + Operators.onErrorDropped(t, this.initialContext); + } + } + } + + @Override + public final void onError(Throwable t) { + Subscription s = S.getAndSet(this, Operators.cancelledSubscription()); + if (s == Operators.cancelledSubscription()) { + Operators.onErrorDropped(t, this.initialContext); + return; + } + doError(t); + } + + void doError(Throwable t) { + if (errorConsumer != null) { + errorConsumer.accept(t); + } + else { + Operators.onErrorDropped(Exceptions.errorCallbackNotImplemented(t), this.initialContext); + } + } + + @Override + public final void onNext(T x) { + Subscription s = S.getAndSet(this, Operators.cancelledSubscription()); + if (s == Operators.cancelledSubscription()) { + Operators.onNextDropped(x, this.initialContext); + return; + } + if (consumer != null) { + try { + consumer.accept(x); + } + catch (Throwable t) { + Exceptions.throwIfFatal(t); + s.cancel(); + doError(t); + } + } + if (completeConsumer != null) { + try { + completeConsumer.run(); + } + catch (Throwable t) { + Operators.onErrorDropped(t, this.initialContext); + } + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return subscription; + } + if (key == Attr.PREFETCH) { + return Integer.MAX_VALUE; + } + if (key == Attr.TERMINATED || key == Attr.CANCELLED) { + return isDisposed(); + } + if (key == RUN_STYLE) { + return SYNC; + } + + return null; + } + + @Override + public boolean isDisposed() { + return subscription == Operators.cancelledSubscription(); + } + + @Override + public void dispose() { + Subscription s = S.getAndSet(this, Operators.cancelledSubscription()); + if (s != null && s != Operators.cancelledSubscription()) { + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/LambdaSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/LambdaSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/LambdaSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2016-2021 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.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + + +/** + * An unbounded Java Lambda adapter to {@link Subscriber} + * + * @param the value type + */ +final class LambdaSubscriber + implements InnerConsumer, Disposable { + + final Consumer consumer; + final Consumer errorConsumer; + final Runnable completeConsumer; + final Consumer subscriptionConsumer; + final Context initialContext; + + volatile Subscription subscription; + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(LambdaSubscriber.class, + Subscription.class, + "subscription"); + + /** + * Create a {@link Subscriber} reacting onNext, onError and onComplete. If no + * {@code subscriptionConsumer} is provided, the subscriber will automatically request + * Long.MAX_VALUE in onSubscribe, as well as an initial {@link Context} that will be + * visible by operators upstream in the chain. + * + * @param consumer A {@link Consumer} with argument onNext data + * @param errorConsumer A {@link Consumer} called onError + * @param completeConsumer A {@link Runnable} called onComplete with the actual + * context if any + * @param subscriptionConsumer A {@link Consumer} called with the {@link Subscription} + * to perform initial request, or null to request max + * @param initialContext A {@link Context} for this subscriber, or null to use the default + * of an {@link Context#empty() empty Context}. + */ + LambdaSubscriber( + @Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer, + @Nullable Consumer subscriptionConsumer, + @Nullable Context initialContext) { + this.consumer = consumer; + this.errorConsumer = errorConsumer; + this.completeConsumer = completeConsumer; + this.subscriptionConsumer = subscriptionConsumer; + this.initialContext = initialContext == null ? Context.empty() : initialContext; + } + + /** + * Create a {@link Subscriber} reacting onNext, onError and onComplete. If no + * {@code subscriptionConsumer} is provided, the subscriber will automatically request + * Long.MAX_VALUE in onSubscribe, as well as an initial {@link Context} that will be + * visible by operators upstream in the chain. + * + * @param consumer A {@link Consumer} with argument onNext data + * @param errorConsumer A {@link Consumer} called onError + * @param completeConsumer A {@link Runnable} called onComplete with the actual + * context if any + * @param subscriptionConsumer A {@link Consumer} called with the {@link Subscription} + * to perform initial request, or null to request max + */ //left mainly for the benefit of tests + LambdaSubscriber( + @Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer, + @Nullable Consumer subscriptionConsumer) { + this(consumer, errorConsumer, completeConsumer, subscriptionConsumer, null); + } + + @Override + public Context currentContext() { + return this.initialContext; + } + + @Override + public final void onSubscribe(Subscription s) { + if (Operators.validate(subscription, s)) { + this.subscription = s; + if (subscriptionConsumer != null) { + try { + subscriptionConsumer.accept(s); + } + catch (Throwable t) { + Exceptions.throwIfFatal(t); + s.cancel(); + onError(t); + } + } + else { + s.request(Long.MAX_VALUE); + } + } + } + + @Override + public final void onComplete() { + Subscription s = S.getAndSet(this, Operators.cancelledSubscription()); + if (s == Operators.cancelledSubscription()) { + return; + } + if (completeConsumer != null) { + try { + completeConsumer.run(); + } + catch (Throwable t) { + Exceptions.throwIfFatal(t); + onError(t); + } + } + } + + @Override + public final void onError(Throwable t) { + Subscription s = S.getAndSet(this, Operators.cancelledSubscription()); + if (s == Operators.cancelledSubscription()) { + Operators.onErrorDropped(t, this.initialContext); + return; + } + if (errorConsumer != null) { + errorConsumer.accept(t); + } + else { + Operators.onErrorDropped(Exceptions.errorCallbackNotImplemented(t), this.initialContext); + } + } + + @Override + public final void onNext(T x) { + try { + if (consumer != null) { + consumer.accept(x); + } + } + catch (Throwable t) { + Exceptions.throwIfFatal(t); + this.subscription.cancel(); + onError(t); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return subscription; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + + @Override + public boolean isDisposed() { + return subscription == Operators.cancelledSubscription(); + } + + @Override + public void dispose() { + Subscription s = S.getAndSet(this, Operators.cancelledSubscription()); + if (s != null && s != Operators.cancelledSubscription()) { + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/Mono.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/Mono.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/Mono.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,5242 @@ +/* + * Copyright (c) 2016-2021 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.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.Spliterator; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.LongConsumer; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.logging.Level; + +import io.micrometer.core.instrument.MeterRegistry; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.Scannable; +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; +import reactor.util.Logger; +import reactor.util.Metrics; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; +import reactor.util.context.ContextView; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuple3; +import reactor.util.function.Tuple4; +import reactor.util.function.Tuple5; +import reactor.util.function.Tuple6; +import reactor.util.function.Tuple7; +import reactor.util.function.Tuple8; +import reactor.util.function.Tuples; +import reactor.util.retry.Retry; + +/** + * A Reactive Streams {@link Publisher} with basic rx operators that emits at most one item via the + * {@code onNext} signal then terminates with an {@code onComplete} signal (successful Mono, + * with or without value), or only emits a single {@code onError} signal (failed Mono). + * + *

Most Mono implementations are expected to immediately call {@link Subscriber#onComplete()} + * after having called {@link Subscriber#onNext(T)}. {@link Mono#never() Mono.never()} is an outlier: it doesn't + * emit any signal, which is not technically forbidden although not terribly useful outside + * of tests. On the other hand, a combination of {@code onNext} and {@code onError} is explicitly forbidden. + * + *

+ * The recommended way to learn about the {@link Mono} API and discover new operators is + * through the reference documentation, rather than through this javadoc (as opposed to + * learning more about individual operators). See the + * "which operator do I need?" appendix. + * + *

+ * + *

+ * + *

The rx operators will offer aliases for input {@link Mono} type to preserve the "at most one" + * property of the resulting {@link Mono}. For instance {@link Mono#flatMap flatMap} returns a + * {@link Mono}, while there is a {@link Mono#flatMapMany flatMapMany} alias with possibly more than + * 1 emission. + * + *

{@code Mono} should be used for {@link Publisher} that just completes without any value. + * + *

It is intended to be used in implementations and return types, input parameters should keep + * using raw {@link Publisher} as much as possible. + * + *

Note that using state in the {@code java.util.function} / lambdas used within Mono operators + * should be avoided, as these may be shared between several {@link Subscriber Subscribers}. + * + * @param the type of the single value of this class + * @author Sebastien Deleuze + * @author Stephane Maldini + * @author David Karnok + * @author Simon Baslé + * @see Flux + */ +public abstract class Mono implements CorePublisher { + +// ============================================================================================================== +// Static Generators +// ============================================================================================================== + + /** + * Creates a deferred emitter that can be used with callback-based + * APIs to signal at most one value, a complete or an error signal. + *

+ * + *

+ * Bridging legacy API involves mostly boilerplate code due to the lack + * of standard types and methods. There are two kinds of API surfaces: + * 1) addListener/removeListener and 2) callback-handler. + *

+ * 1) addListener/removeListener pairs
+ * To work with such API one has to instantiate the listener, + * call the sink from the listener then register it with the source: + *


+	 * Mono.<String>create(sink -> {
+	 *     HttpListener listener = event -> {
+	 *         if (event.getResponseCode() >= 400) {
+	 *             sink.error(new RuntimeException("Failed"));
+	 *         } else {
+	 *             String body = event.getBody();
+	 *             if (body.isEmpty()) {
+	 *                 sink.success();
+	 *             } else {
+	 *                 sink.success(body.toLowerCase());
+	 *             }
+	 *         }
+	 *     };
+	 *
+	 *     client.addListener(listener);
+	 *
+	 *     sink.onDispose(() -> client.removeListener(listener));
+	 * });
+	 * 
+ * Note that this works only with single-value emitting listeners. Otherwise, + * all subsequent signals are dropped. You may have to add {@code client.removeListener(this);} + * to the listener's body. + *

+ * 2) callback handler
+ * This requires a similar instantiation pattern such as above, but usually the + * successful completion and error are separated into different methods. + * In addition, the legacy API may or may not support some cancellation mechanism. + *


+	 * Mono.<String>create(sink -> {
+	 *     Callback<String> callback = new Callback<String>() {
+	 *         @Override
+	 *         public void onResult(String data) {
+	 *             sink.success(data.toLowerCase());
+	 *         }
+	 *
+	 *         @Override
+	 *         public void onError(Exception e) {
+	 *             sink.error(e);
+	 *         }
+	 *     }
+	 *
+	 *     // without cancellation support:
+	 *
+	 *     client.call("query", callback);
+	 *
+	 *     // with cancellation support:
+	 *
+	 *     AutoCloseable cancel = client.call("query", callback);
+	 *     sink.onDispose(() -> {
+	 *         try {
+	 *             cancel.close();
+	 *         } catch (Exception ex) {
+	 *             Exceptions.onErrorDropped(ex);
+	 *         }
+	 *     });
+	 * });
+	 * 
+ * @param callback Consume the {@link MonoSink} provided per-subscriber by Reactor to generate signals. + * @param The type of the value emitted + * @return a {@link Mono} + */ + public static Mono create(Consumer> callback) { + return onAssembly(new MonoCreate<>(callback)); + } + + /** + * Create a {@link Mono} provider that will {@link Supplier#get supply} a target {@link Mono} to subscribe to for + * each {@link Subscriber} downstream. + * + *

+ * + *

+ * @param supplier a {@link Mono} factory + * @param the element type of the returned Mono instance + * @return a deferred {@link Mono} + * @see #deferContextual(Function) + */ + public static Mono defer(Supplier> supplier) { + return onAssembly(new MonoDefer<>(supplier)); + } + + /** + * 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. + * + *

+ * + *

+ * @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 + */ + public static Mono deferContextual(Function> contextualMonoFactory) { + return onAssembly(new MonoDeferContextual<>(contextualMonoFactory)); + } + + /** + * Create a Mono which delays an onNext signal by a given {@link Duration duration} + * on a default Scheduler and completes. + * If the demand cannot be produced in time, an onError will be signalled instead. + * The delay is introduced through the {@link Schedulers#parallel() parallel} default Scheduler. + * + *

+ * + *

+ * @param duration the duration of the delay + * + * @return a new {@link Mono} + */ + public static Mono delay(Duration duration) { + return delay(duration, Schedulers.parallel()); + } + + /** + * Create a Mono which delays an onNext signal by a given {@link Duration duration} + * on a provided {@link Scheduler} and completes. + * If the demand cannot be produced in time, an onError will be signalled instead. + * + *

+ * + *

+ * @param duration the {@link Duration} of the delay + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a new {@link Mono} + */ + public static Mono delay(Duration duration, Scheduler timer) { + return onAssembly(new MonoDelay(duration.toNanos(), TimeUnit.NANOSECONDS, timer)); + } + + /** + * Create a {@link Mono} that completes without emitting any item. + * + *

+ * + *

+ * @param the reified {@link Subscriber} type + * + * @return a completed {@link Mono} + */ + public static Mono empty() { + return MonoEmpty.instance(); + } + + /** + * Create a {@link Mono} that terminates with the specified error immediately after + * being subscribed to. + *

+ * + *

+ * @param error the onError signal + * @param the reified {@link Subscriber} type + * + * @return a failing {@link Mono} + */ + public static Mono error(Throwable error) { + return onAssembly(new MonoError<>(error)); + } + + /** + * Create a {@link Mono} that terminates with an error immediately after being + * subscribed to. The {@link Throwable} is generated by a {@link Supplier}, invoked + * each time there is a subscription and allowing for lazy instantiation. + *

+ * + *

+ * @param errorSupplier the error signal {@link Supplier} to invoke for each {@link Subscriber} + * @param the reified {@link Subscriber} type + * + * @return a failing {@link Mono} + */ + public static Mono error(Supplier errorSupplier) { + return onAssembly(new MonoErrorSupplied<>(errorSupplier)); + } + + /** + * Pick the first {@link Mono} to emit any signal (value, empty completion or error) + * and replay that signal, effectively behaving like the fastest of these competing + * sources. + *

+ * + *

+ * @param monos The deferred monos to use. + * @param The type of the function result. + * + * @return a new {@link Mono} behaving like the fastest of its sources. + * @deprecated use {@link #firstWithSignal(Mono[])}. To be removed in reactor 3.5. + */ + @SafeVarargs + @Deprecated + public static Mono first(Mono... monos) { + return firstWithSignal(monos); + } + + /** + * Pick the first {@link Mono} to emit any signal (value, empty completion or error) + * and replay that signal, effectively behaving like the fastest of these competing + * sources. + *

+ * + *

+ * @param monos The deferred monos to use. + * @param The type of the function result. + * + * @return a new {@link Mono} behaving like the fastest of its sources. + * @deprecated use {@link #firstWithSignal(Iterable)}. To be removed in reactor 3.5. + */ + @Deprecated + public static Mono first(Iterable> monos) { + return firstWithSignal(monos); + } + + /** + * Pick the first {@link Mono} to emit any signal (value, empty completion or error) + * and replay that signal, effectively behaving like the fastest of these competing + * sources. + *

+ * + *

+ * @param monos The deferred monos to use. + * @param The type of the function result. + * + * @return a new {@link Mono} behaving like the fastest of its sources. + */ + @SafeVarargs + public static Mono firstWithSignal(Mono... monos) { + return onAssembly(new MonoFirstWithSignal<>(monos)); + } + + /** + * Pick the first {@link Mono} to emit any signal (value, empty completion or error) + * and replay that signal, effectively behaving like the fastest of these competing + * sources. + *

+ * + *

+ * @param monos The deferred monos to use. + * @param The type of the function result. + * + * @return a new {@link Mono} behaving like the fastest of its sources. + */ + public static Mono firstWithSignal(Iterable> monos) { + return onAssembly(new MonoFirstWithSignal<>(monos)); + } + + /** + * Pick the first {@link Mono} source to emit any value and replay that signal, + * effectively behaving like the source that first emits an + * {@link Subscriber#onNext(Object) onNext}. + * + *

+ * Valued sources always "win" over an empty source (one that only emits onComplete) + * or a failing source (one that only emits onError). + *

+ * When no source can provide a value, this operator fails with a {@link NoSuchElementException} + * (provided there are at least two sources). This exception has a {@link Exceptions#multiple(Throwable...) composite} + * as its {@link Throwable#getCause() cause} that can be used to inspect what went wrong with each source + * (so the composite has as many elements as there are sources). + *

+ * Exceptions from failing sources are directly reflected in the composite at the index of the failing source. + * For empty sources, a {@link NoSuchElementException} is added at their respective index. + * One can use {@link Exceptions#unwrapMultiple(Throwable) Exceptions.unwrapMultiple(topLevel.getCause())} + * to easily inspect these errors as a {@link List}. + *

+ * Note that like in {@link #firstWithSignal(Iterable)}, an infinite source can be problematic + * if no other source emits onNext. + *

+ * + * + * @param monos An {@link Iterable} of the competing source monos + * @param The type of the element in the sources and the resulting mono + * + * @return a new {@link Mono} behaving like the fastest of its sources + */ + public static Mono firstWithValue(Iterable> monos) { + return onAssembly(new MonoFirstWithValue<>(monos)); + } + + /** + * Pick the first {@link Mono} source to emit any value and replay that signal, + * effectively behaving like the source that first emits an + * {@link Subscriber#onNext(Object) onNext}. + *

+ * Valued sources always "win" over an empty source (one that only emits onComplete) + * or a failing source (one that only emits onError). + *

+ * When no source can provide a value, this operator fails with a {@link NoSuchElementException} + * (provided there are at least two sources). This exception has a {@link Exceptions#multiple(Throwable...) composite} + * as its {@link Throwable#getCause() cause} that can be used to inspect what went wrong with each source + * (so the composite has as many elements as there are sources). + *

+ * Exceptions from failing sources are directly reflected in the composite at the index of the failing source. + * For empty sources, a {@link NoSuchElementException} is added at their respective index. + * One can use {@link Exceptions#unwrapMultiple(Throwable) Exceptions.unwrapMultiple(topLevel.getCause())} + * to easily inspect these errors as a {@link List}. + *

+ * Note that like in {@link #firstWithSignal(Mono[])}, an infinite source can be problematic + * if no other source emits onNext. + * In case the {@code first} source is already an array-based {@link #firstWithValue(Mono, Mono[])} + * instance, nesting is avoided: a single new array-based instance is created with all the + * sources from {@code first} plus all the {@code others} sources at the same level. + *

+ * + * + * @param first the first competing source {@link Mono} + * @param others the other competing sources {@link Mono} + * @param The type of the element in the sources and the resulting mono + * + * @return a new {@link Mono} behaving like the fastest of its sources + */ + @SafeVarargs + public static Mono firstWithValue(Mono first, Mono... others) { + if (first instanceof MonoFirstWithValue) { + @SuppressWarnings("unchecked") + MonoFirstWithValue a = (MonoFirstWithValue) first; + Mono result = a.firstValuedAdditionalSources(others); + if (result != null) { + return result; + } + } + return onAssembly(new MonoFirstWithValue<>(first, others)); + } + + /** + * Expose the specified {@link Publisher} with the {@link Mono} API, and ensure it will emit 0 or 1 item. + * The source emitter will be cancelled on the first `onNext`. + *

+ * + *

+ * {@link Hooks#onEachOperator(String, Function)} and similar assembly hooks are applied + * unless the source is already a {@link Mono} (including {@link Mono} that was decorated as a {@link Flux}, + * see {@link Flux#from(Publisher)}). + * + * @param source the {@link Publisher} source + * @param the source type + * + * @return the next item emitted as a {@link Mono} + */ + public static Mono from(Publisher source) { + //some sources can be considered already assembled monos + //all conversion methods (from, fromDirect, wrap) must accommodate for this + if (source instanceof Mono) { + @SuppressWarnings("unchecked") + Mono casted = (Mono) source; + return casted; + } + if (source instanceof FluxSourceMono + || source instanceof FluxSourceMonoFuseable) { + @SuppressWarnings("unchecked") + FluxFromMonoOperator wrapper = (FluxFromMonoOperator) source; + @SuppressWarnings("unchecked") + Mono extracted = (Mono) wrapper.source; + return extracted; + } + + //we delegate to `wrap` and apply assembly hooks + @SuppressWarnings("unchecked") Publisher downcasted = (Publisher) source; + return onAssembly(wrap(downcasted, true)); + } + + /** + * Create a {@link Mono} producing its value using the provided {@link Callable}. If + * the Callable resolves to {@code null}, the resulting Mono completes empty. + * + *

+ * + *

+ * @param supplier {@link Callable} that will produce the value + * @param type of the expected value + * + * @return A {@link Mono}. + */ + public static Mono fromCallable(Callable supplier) { + return onAssembly(new MonoCallable<>(supplier)); + } + + /** + * Create a {@link Mono}, producing its value using the provided {@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)}. + * + * @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)); + } + + /** + * Create a {@link Mono} that wraps a {@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)}. + * + * @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()))); + } + + /** + * Convert a {@link Publisher} to a {@link Mono} without any cardinality check + * (ie this method doesn't cancel the source past the first element). + * Conversion transparently returns {@link Mono} sources without wrapping and otherwise + * supports {@link Fuseable} sources. + * Note this is an advanced interoperability operator that implies you know the + * {@link Publisher} you are converting follows the {@link Mono} semantics and only + * ever emits one element. + *

+ * {@link Hooks#onEachOperator(String, Function)} and similar assembly hooks are applied + * unless the source is already a {@link Mono}. + * + * @param source the Mono-compatible {@link Publisher} to wrap + * @param type of the value emitted by the publisher + * @return a wrapped {@link Mono} + */ + public static Mono fromDirect(Publisher source){ + //some sources can be considered already assembled monos + //all conversion methods (from, fromDirect, wrap) must accommodate for this + if(source instanceof Mono){ + @SuppressWarnings("unchecked") + Mono m = (Mono)source; + return m; + } + if (source instanceof FluxSourceMono + || source instanceof FluxSourceMonoFuseable) { + @SuppressWarnings("unchecked") + FluxFromMonoOperator wrapper = (FluxFromMonoOperator) source; + @SuppressWarnings("unchecked") + Mono extracted = (Mono) wrapper.source; + return extracted; + } + + //we delegate to `wrap` and apply assembly hooks + @SuppressWarnings("unchecked") Publisher downcasted = (Publisher) source; + return onAssembly(wrap(downcasted, false)); + } + + /** + * Create a {@link Mono}, producing its value using the provided {@link CompletableFuture}. + * + *

+ * + *

+ * 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 future {@link CompletableFuture} that will produce a value (or a null to + * complete immediately) + * @param type of the expected value + * @return A {@link Mono}. + * @see #fromCompletionStage(CompletionStage) fromCompletionStage for a generalization + */ + public static Mono fromFuture(CompletableFuture future) { + return onAssembly(new MonoCompletionStage<>(future)); + } + + /** + * Create a {@link Mono} that wraps a {@link CompletableFuture} on subscription, + * emitting the value produced by the Future. + * + *

+ * + *

+ * 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 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()))); + } + + /** + * Create a {@link Mono} that completes empty once the provided {@link Runnable} has + * been executed. + * + *

+ * + *

+ * @param runnable {@link Runnable} that will be executed before emitting the completion signal + * + * @param The generic type of the upstream, which is preserved by this operator + * @return A {@link Mono}. + */ + public static Mono fromRunnable(Runnable runnable) { + return onAssembly(new MonoRunnable<>(runnable)); + } + + /** + * Create a {@link Mono}, producing its value using the provided {@link Supplier}. If + * the Supplier resolves to {@code null}, the resulting Mono completes empty. + * + *

+ * + *

+ * @param supplier {@link Supplier} that will produce the value + * @param type of the expected value + * + * @return A {@link Mono}. + */ + public static Mono fromSupplier(Supplier supplier) { + return onAssembly(new MonoSupplier<>(supplier)); + } + + + /** + * Create a new {@link Mono} that ignores elements from the source (dropping them), + * but completes when the source completes. + * + *

+ * + *

+ * + *

Discard Support: This operator discards the element from the source. + * + * @param source the {@link Publisher} to ignore + * @param the source type of the ignored data + * + * @return a new completable {@link Mono}. + */ + public static Mono ignoreElements(Publisher source) { + return onAssembly(new MonoIgnorePublisher<>(source)); + } + + /** + * Create a new {@link Mono} that emits the specified item, which is captured at + * instantiation time. + * + *

+ * + *

+ * @param data the only item to onNext + * @param the type of the produced item + * + * @return a {@link Mono}. + */ + public static Mono just(T data) { + return onAssembly(new MonoJust<>(data)); + } + + /** + * Create a new {@link Mono} that emits the specified item if {@link Optional#isPresent()} otherwise only emits + * onComplete. + * + *

+ * + *

+ * @param data the {@link Optional} item to onNext or onComplete if not present + * @param the type of the produced item + * + * @return a {@link Mono}. + */ + public static Mono justOrEmpty(@Nullable Optional data) { + return data != null && data.isPresent() ? just(data.get()) : empty(); + } + + /** + * Create a new {@link Mono} that emits the specified item if non null otherwise only emits + * onComplete. + * + *

+ * + *

+ * @param data the item to onNext or onComplete if null + * @param the type of the produced item + * + * @return a {@link Mono}. + */ + public static Mono justOrEmpty(@Nullable T data) { + return data != null ? just(data) : empty(); + } + + + /** + * Return a {@link Mono} that will never signal any data, error or completion signal, + * essentially running indefinitely. + *

+ * + *

+ * @param the {@link Subscriber} type target + * + * @return a never completing {@link Mono} + */ + public static Mono never() { + return MonoNever.instance(); + } + + /** + * Returns a Mono that emits a Boolean value that indicates whether two Publisher sequences are the + * same by comparing the items emitted by each Publisher pairwise. + *

+ * + * + * @param source1 the first Publisher to compare + * @param source2 the second Publisher to compare + * @param the type of items emitted by each Publisher + * @return a Mono that emits a Boolean value that indicates whether the two sequences are the same + */ + public static Mono sequenceEqual(Publisher source1, Publisher source2) { + return sequenceEqual(source1, source2, equalsBiPredicate(), Queues.SMALL_BUFFER_SIZE); + } + + /** + * Returns a Mono that emits a Boolean value that indicates whether two Publisher sequences are the + * same by comparing the items emitted by each Publisher pairwise based on the results of a specified + * equality function. + *

+ * + * + * @param source1 the first Publisher to compare + * @param source2 the second Publisher to compare + * @param isEqual a function used to compare items emitted by each Publisher + * @param the type of items emitted by each Publisher + * @return a Mono that emits a Boolean value that indicates whether the two Publisher two sequences + * are the same according to the specified function + */ + public static Mono sequenceEqual(Publisher source1, Publisher source2, + BiPredicate isEqual) { + return sequenceEqual(source1, source2, isEqual, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Returns a Mono that emits a Boolean value that indicates whether two Publisher sequences are the + * same by comparing the items emitted by each Publisher pairwise based on the results of a specified + * equality function. + *

+ * + * + * @param source1 the first Publisher to compare + * @param source2 the second Publisher to compare + * @param isEqual a function used to compare items emitted by each Publisher + * @param prefetch the number of items to prefetch from the first and second source Publisher + * @param the type of items emitted by each Publisher + * @return a Mono that emits a Boolean value that indicates whether the two Publisher two sequences + * are the same according to the specified function + */ + public static Mono sequenceEqual(Publisher source1, + Publisher source2, + BiPredicate isEqual, int prefetch) { + return onAssembly(new MonoSequenceEqual<>(source1, source2, isEqual, prefetch)); + } + + /** + * 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. + *

+ *

    + *
  • For eager cleanup, unlike in {@link Flux#using(Callable, Function, Consumer, boolean) Flux}, + * in the case of a valued {@link Mono} the cleanup happens just before passing the value to downstream. + * In all cases, exceptions raised by the eager cleanup {@link Consumer} may override the terminal event, + * discarding the element if the derived {@link Mono} was valued.
  • + *
  • Non-eager cleanup will drop any exception.
  • + *
+ *

+ * + * + * @param resourceSupplier a {@link Callable} that is called on subscribe to create the resource + * @param sourceSupplier a {@link Mono} factory to create the Mono depending on the created resource + * @param resourceCleanup invoked on completion to clean-up the resource + * @param eager set to true to clean before any signal (including onNext) is passed downstream + * @param emitted type + * @param resource type + * + * @return new {@link Mono} + */ + public static Mono using(Callable resourceSupplier, + Function> sourceSupplier, + Consumer resourceCleanup, + boolean eager) { + return onAssembly(new MonoUsing<>(resourceSupplier, sourceSupplier, + resourceCleanup, eager)); + } + + /** + * 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. + *

+ * Unlike in {@link Flux#using(Callable, Function, Consumer) Flux}, in the case of a valued {@link Mono} the cleanup + * happens just before passing the value to downstream. In all cases, exceptions raised by the cleanup + * {@link Consumer} may override the terminal event, discarding the element if the derived {@link Mono} was valued. + *

+ * + * + * @param resourceSupplier a {@link Callable} that is called on subscribe to create the resource + * @param sourceSupplier a {@link Mono} factory to create the Mono depending on the created resource + * @param resourceCleanup invoked on completion to clean-up the resource + * @param emitted type + * @param resource type + * + * @return new {@link Mono} + */ + public static Mono using(Callable resourceSupplier, + Function> sourceSupplier, + Consumer resourceCleanup) { + return using(resourceSupplier, sourceSupplier, resourceCleanup, true); + } + + + /** + * Uses a resource, generated by a {@link Publisher} for each individual {@link Subscriber}, + * to derive a {@link Mono}. Note that all steps of the operator chain that would need the + * resource to be in an open stable state need to be described inside the {@code resourceClosure} + * {@link Function}. + *

+ * Unlike in {@link Flux#usingWhen(Publisher, Function, Function) the Flux counterpart}, ALL signals are deferred + * until the {@link Mono} terminates and the relevant {@link Function} generates and invokes a "cleanup" + * {@link Publisher}. This is because a failure in the cleanup Publisher + * must result in a lone {@code onError} signal in the downstream {@link Mono} (any potential value in the + * derived {@link Mono} is discarded). Here are the various scenarios that can play out: + *

    + *
  • empty Mono, asyncCleanup ends with {@code onComplete()}: downstream receives {@code onComplete()}
  • + *
  • empty Mono, asyncCleanup ends with {@code onError(t)}: downstream receives {@code onError(t)}
  • + *
  • valued Mono, asyncCleanup ends with {@code onComplete()}: downstream receives {@code onNext(value),onComplete()}
  • + *
  • valued Mono, asyncCleanup ends with {@code onError(t)}: downstream receives {@code onError(t)}, {@code value} is discarded
  • + *
  • error(e) Mono, asyncCleanup ends with {@code onComplete()}: downstream receives {@code onError(e)}
  • + *
  • error(e) Mono, asyncCleanup ends with {@code onError(t)}: downstream receives {@code onError(t)}, t suppressing e
  • + *
+ *

+ * + *

+ * Note that if the resource supplying {@link Publisher} emits more than one resource, the + * subsequent resources are dropped ({@link Operators#onNextDropped(Object, Context)}). If + * the publisher errors AFTER having emitted one resource, the error is also silently dropped + * ({@link Operators#onErrorDropped(Throwable, Context)}). + * An empty completion or error without at least one onNext signal (no resource supplied) + * triggers a short-circuit of the main sequence with the same terminal signal + * (no cleanup is invoked). + * + *

Discard Support: This operator discards any source element if the {@code asyncCleanup} handler fails. + * + * @param resourceSupplier a {@link Publisher} that "generates" the resource, + * subscribed for each subscription to the main sequence + * @param resourceClosure a factory to derive a {@link Mono} from the supplied resource + * @param asyncCleanup an asynchronous resource cleanup invoked when the resource + * closure terminates (with onComplete, onError or cancel) + * @param the type of elements emitted by the resource closure, and thus the main sequence + * @param the type of the resource object + * + * @return a new {@link Mono} built around a "transactional" resource, with deferred emission until the + * asynchronous cleanup sequence completes + */ + public static Mono usingWhen(Publisher resourceSupplier, + Function> resourceClosure, + Function> asyncCleanup) { + return usingWhen(resourceSupplier, resourceClosure, asyncCleanup, + (res, error) -> asyncCleanup.apply(res), + asyncCleanup); + } + + /** + * Uses a resource, generated by a {@link Publisher} for each individual {@link Subscriber}, + * to derive a {@link Mono}.Note that all steps of the operator chain that would need the + * resource to be in an open stable state need to be described inside the {@code resourceClosure} + * {@link Function}. + *

+ * Unlike in {@link Flux#usingWhen(Publisher, Function, Function, BiFunction, Function) the Flux counterpart}, + * ALL signals are deferred until the {@link Mono} terminates and the relevant {@link Function} + * generates and invokes a "cleanup" {@link Publisher}. This is because a failure in the cleanup Publisher + * must result in a lone {@code onError} signal in the downstream {@link Mono} (any potential value in the + * derived {@link Mono} is discarded). Here are the various scenarios that can play out: + *

    + *
  • empty Mono, asyncComplete ends with {@code onComplete()}: downstream receives {@code onComplete()}
  • + *
  • empty Mono, asyncComplete ends with {@code onError(t)}: downstream receives {@code onError(t)}
  • + *
  • valued Mono, asyncComplete ends with {@code onComplete()}: downstream receives {@code onNext(value),onComplete()}
  • + *
  • valued Mono, asyncComplete ends with {@code onError(t)}: downstream receives {@code onError(t)}, {@code value} is discarded
  • + *
  • error(e) Mono, errorComplete ends with {@code onComplete()}: downstream receives {@code onError(e)}
  • + *
  • error(e) Mono, errorComplete ends with {@code onError(t)}: downstream receives {@code onError(t)}, t suppressing e
  • + *
+ *

+ * + *

+ * Individual cleanups can also be associated with mono cancellation and + * error terminations: + *

+ * + *

+ * Note that if the resource supplying {@link Publisher} emits more than one resource, the + * subsequent resources are dropped ({@link Operators#onNextDropped(Object, Context)}). If + * the publisher errors AFTER having emitted one resource, the error is also silently dropped + * ({@link Operators#onErrorDropped(Throwable, Context)}). + * An empty completion or error without at least one onNext signal (no resource supplied) + * triggers a short-circuit of the main sequence with the same terminal signal + * (no cleanup is invoked). + * + *

Discard Support: This operator discards the element if the {@code asyncComplete} handler fails. + * + * @param resourceSupplier a {@link Publisher} that "generates" the resource, + * subscribed for each subscription to the main sequence + * @param resourceClosure a factory to derive a {@link Mono} from the supplied resource + * @param asyncComplete an asynchronous resource cleanup invoked if the resource closure terminates with onComplete + * @param asyncError an asynchronous resource cleanup invoked if the resource closure terminates with onError. + * The terminating error is provided to the {@link BiFunction} + * @param asyncCancel an asynchronous resource cleanup invoked if the resource closure is cancelled. + * When {@code null}, the {@code asyncComplete} path is used instead. + * @param the type of elements emitted by the resource closure, and thus the main sequence + * @param the type of the resource object + * + * @return a new {@link Mono} built around a "transactional" resource, with several + * termination path triggering asynchronous cleanup sequences + * + */ + public static Mono usingWhen(Publisher resourceSupplier, + Function> resourceClosure, + Function> asyncComplete, + BiFunction> asyncError, + //the operator itself accepts null for asyncCancel, but we won't in the public API + Function> asyncCancel) { + return onAssembly(new MonoUsingWhen<>(resourceSupplier, resourceClosure, + asyncComplete, asyncError, asyncCancel)); + } + + /** + * Aggregate given publishers into a new {@literal Mono} that will be fulfilled + * when all of the given {@literal sources} have completed. An error will cause + * pending results to be cancelled and immediate error emission to the returned {@link Mono}. + *

+ * + *

+ * @param sources The sources to use. + * + * @return a {@link Mono}. + */ + public static Mono when(Publisher... sources) { + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + return empty(sources[0]); + } + return onAssembly(new MonoWhen(false, sources)); + } + + + /** + * Aggregate given publishers into a new {@literal Mono} that will be + * fulfilled when all of the given {@literal Publishers} have completed. + * An error will cause pending results to be cancelled and immediate error emission + * to the returned {@link Mono}. + * + *

+ * + *

+ * + * @param sources The sources to use. + * + * @return a {@link Mono}. + */ + public static Mono when(final Iterable> sources) { + return onAssembly(new MonoWhen(false, sources)); + } + + /** + * Aggregate given publishers into a new {@literal Mono} that will be + * fulfilled when all of the given {@literal sources} have completed. Errors from + * the sources are delayed. + * If several Publishers error, the exceptions are combined (as suppressed exceptions on a root exception). + * + *

+ * + *

+ * + * @param sources The sources to use. + * + * @return a {@link Mono}. + */ + public static Mono whenDelayError(final Iterable> sources) { + return onAssembly(new MonoWhen(true, sources)); + } + + /** + * Merge given publishers into a new {@literal Mono} that will be fulfilled when + * all of the given {@literal sources} have completed. Errors from the sources are delayed. + * If several Publishers error, the exceptions are combined (as suppressed exceptions on a root exception). + * + *

+ * + *

+ * @param sources The sources to use. + * + * @return a {@link Mono}. + */ + public static Mono whenDelayError(Publisher... sources) { + if (sources.length == 0) { + return empty(); + } + if (sources.length == 1) { + return empty(sources[0]); + } + return onAssembly(new MonoWhen(true, sources)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple2}. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * + * @return a {@link Mono}. + */ + public static Mono> zip(Mono p1, Mono p2) { + return zip(p1, p2, Flux.tuple2Function()); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values as defined by the combinator function. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param combinator a {@link BiFunction} combinator function when both sources + * complete + * @param type of the value from p1 + * @param type of the value from p2 + * @param output value + * + * @return a {@link Mono}. + */ + public static Mono zip(Mono p1, Mono p2, BiFunction combinator) { + return onAssembly(new MonoZip(false, p1, p2, combinator)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple3}. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zip(Mono p1, Mono p2, Mono p3) { + return onAssembly(new MonoZip(false, a -> Tuples.fromArray((Object[])a), p1, p2, p3)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple4}. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zip(Mono p1, + Mono p2, + Mono p3, + Mono p4) { + return onAssembly(new MonoZip(false, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple5}. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param p5 The fifth upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * @param type of the value from p5 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zip(Mono p1, + Mono p2, + Mono p3, + Mono p4, + Mono p5) { + return onAssembly(new MonoZip(false, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4, p5)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple6}. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param p5 The fifth upstream {@link Publisher} to subscribe to. + * @param p6 The sixth upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * @param type of the value from p5 + * @param type of the value from p6 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zip(Mono p1, + Mono p2, + Mono p3, + Mono p4, + Mono p5, + Mono p6) { + return onAssembly(new MonoZip(false, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4, p5, p6)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple7}. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param p5 The fifth upstream {@link Publisher} to subscribe to. + * @param p6 The sixth upstream {@link Publisher} to subscribe to. + * @param p7 The seventh upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * @param type of the value from p5 + * @param type of the value from p6 + * @param type of the value from p7 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zip(Mono p1, + Mono p2, + Mono p3, + Mono p4, + Mono p5, + Mono p6, + Mono p7) { + return onAssembly(new MonoZip(false, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4, p5, p6, p7)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple8}. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param p5 The fifth upstream {@link Publisher} to subscribe to. + * @param p6 The sixth upstream {@link Publisher} to subscribe to. + * @param p7 The seventh upstream {@link Publisher} to subscribe to. + * @param p8 The eight upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * @param type of the value from p5 + * @param type of the value from p6 + * @param type of the value from p7 + * @param type of the value from p8 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zip(Mono p1, + Mono p2, + Mono p3, + Mono p4, + Mono p5, + Mono p6, + Mono p7, + Mono p8) { + return onAssembly(new MonoZip(false, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4, p5, p6, p7, p8)); + } + + /** + * Aggregate given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal + * Monos} have produced an item, aggregating their values according to the provided combinator function. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + *

+ * + * @param monos The monos to use. + * @param combinator the function to transform the combined array into an arbitrary + * object. + * @param the combined result + * + * @return a {@link Mono}. + */ + public static Mono zip(final Iterable> monos, Function combinator) { + return onAssembly(new MonoZip<>(false, combinator, monos)); + } + + /** + * Aggregate given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal + * Monos} have produced an item, aggregating their values according to the provided combinator function. + * An error or empty completion of any source will cause other sources + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + *

+ * + *

+ * @param monos The monos to use. + * @param combinator the function to transform the combined array into an arbitrary + * object. + * @param the combined result + * + * @return a {@link Mono}. + */ + public static Mono zip(Function combinator, Mono... monos) { + if (monos.length == 0) { + return empty(); + } + if (monos.length == 1) { + return monos[0].map(d -> combinator.apply(new Object[]{d})); + } + return onAssembly(new MonoZip<>(false, combinator, monos)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple2} and delaying errors. + * If a Mono source completes without value, the other source is run to completion then the + * resulting {@link Mono} completes empty. + * If both Monos error, the two exceptions are combined (as suppressed exceptions on a root exception). + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zipDelayError(Mono p1, Mono p2) { + return onAssembly(new MonoZip(true, a -> Tuples.fromArray((Object[])a), p1, p2)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Mono Monos} + * have produced an item, aggregating their values into a {@link Tuple3} and delaying errors. + * If a Mono source completes without value, all other sources are run to completion then + * the resulting {@link Mono} completes empty. + * If several Monos error, their exceptions are combined (as suppressed exceptions on a root exception). + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zipDelayError(Mono p1, Mono p2, Mono p3) { + return onAssembly(new MonoZip(true, a -> Tuples.fromArray((Object[])a), p1, p2, p3)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple4} and delaying errors. + * If a Mono source completes without value, all other sources are run to completion then + * the resulting {@link Mono} completes empty. + * If several Monos error, their exceptions are combined (as suppressed exceptions on a root exception). + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zipDelayError(Mono p1, + Mono p2, + Mono p3, + Mono p4) { + return onAssembly(new MonoZip(true, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple5} and delaying errors. + * If a Mono source completes without value, all other sources are run to completion then + * the resulting {@link Mono} completes empty. + * If several Monos error, their exceptions are combined (as suppressed exceptions on a root exception). + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param p5 The fifth upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * @param type of the value from p5 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zipDelayError(Mono p1, + Mono p2, + Mono p3, + Mono p4, + Mono p5) { + return onAssembly(new MonoZip(true, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4, p5)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple6} and delaying errors. + * If a Mono source completes without value, all other sources are run to completion then + * the resulting {@link Mono} completes empty. + * If several Monos error, their exceptions are combined (as suppressed exceptions on a root exception). + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param p5 The fifth upstream {@link Publisher} to subscribe to. + * @param p6 The sixth upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * @param type of the value from p5 + * @param type of the value from p6 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zipDelayError(Mono p1, + Mono p2, + Mono p3, + Mono p4, + Mono p5, + Mono p6) { + return onAssembly(new MonoZip(true, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4, p5, p6)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple7} and delaying errors. + * If a Mono source completes without value, all other sources are run to completion then + * the resulting {@link Mono} completes empty. + * If several Monos error, their exceptions are combined (as suppressed exceptions on a root exception). + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param p5 The fifth upstream {@link Publisher} to subscribe to. + * @param p6 The sixth upstream {@link Publisher} to subscribe to. + * @param p7 The seventh upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * @param type of the value from p5 + * @param type of the value from p6 + * @param type of the value from p7 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zipDelayError(Mono p1, + Mono p2, + Mono p3, + Mono p4, + Mono p5, + Mono p6, + Mono p7) { + return onAssembly(new MonoZip(true, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4, p5, p6, p7)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal Monos} + * have produced an item, aggregating their values into a {@link Tuple8} and delaying errors. + * If a Mono source completes without value, all other sources are run to completion then + * the resulting {@link Mono} completes empty. + * If several Monos error, their exceptions are combined (as suppressed exceptions on a root exception). + * + *

+ * + *

+ * @param p1 The first upstream {@link Publisher} to subscribe to. + * @param p2 The second upstream {@link Publisher} to subscribe to. + * @param p3 The third upstream {@link Publisher} to subscribe to. + * @param p4 The fourth upstream {@link Publisher} to subscribe to. + * @param p5 The fifth upstream {@link Publisher} to subscribe to. + * @param p6 The sixth upstream {@link Publisher} to subscribe to. + * @param p7 The seventh upstream {@link Publisher} to subscribe to. + * @param p8 The eight upstream {@link Publisher} to subscribe to. + * @param type of the value from p1 + * @param type of the value from p2 + * @param type of the value from p3 + * @param type of the value from p4 + * @param type of the value from p5 + * @param type of the value from p6 + * @param type of the value from p7 + * @param type of the value from p8 + * + * @return a {@link Mono}. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Mono> zipDelayError(Mono p1, + Mono p2, + Mono p3, + Mono p4, + Mono p5, + Mono p6, + Mono p7, + Mono p8) { + return onAssembly(new MonoZip(true, a -> Tuples.fromArray((Object[])a), p1, p2, p3, p4, p5, p6, p7, p8)); + } + + /** + * Aggregate given monos into a new {@literal Mono} that will be fulfilled when all of the given {@literal + * Monos} have produced an item. Errors from the sources are delayed. + * If a Mono source completes without value, all other sources are run to completion then + * the resulting {@link Mono} completes empty. + * If several Monos error, their exceptions are combined (as suppressed exceptions on a root exception). + *

+ * + *

+ * + * @param monos The monos to use. + * @param combinator the function to transform the combined array into an arbitrary + * object. + * @param the combined result + * + * @return a {@link Mono}. + */ + public static Mono zipDelayError(final Iterable> monos, Function combinator) { + return onAssembly(new MonoZip<>(true, combinator, monos)); + } + + /** + * Merge given monos into a new {@literal Mono} that will be fulfilled when all of the + * given {@literal Monos} have produced an item, aggregating their values according to + * the provided combinator function and delaying errors. + * If a Mono source completes without value, all other sources are run to completion then + * the resulting {@link Mono} completes empty. + * If several Monos error, their exceptions are combined (as suppressed exceptions on a root exception). + * + *

+ * + *

+ * @param monos The monos to use. + * @param combinator the function to transform the combined array into an arbitrary + * object. + * @param the combined result + * + * @return a combined {@link Mono}. + */ + public static Mono zipDelayError(Function + combinator, Mono... monos) { + if (monos.length == 0) { + return empty(); + } + if (monos.length == 1) { + return monos[0].map(d -> combinator.apply(new Object[]{d})); + } + return onAssembly(new MonoZip<>(true, combinator, monos)); + } + +// ============================================================================================================== +// Operators +// ============================================================================================================== + + /** + * Transform this {@link Mono} into a target type. + * + *

+	 * {@code mono.as(Flux::from).subscribe() }
+	 * 
+ * + * @param transformer the {@link Function} to immediately map this {@link Mono} + * into a target type + * @param

the returned instance type + * + * @return the {@link Mono} transformed to an instance of P + * @see #transformDeferred(Function) transformDeferred(Function) for a lazy transformation of Mono + */ + public final

P as(Function, P> transformer) { + return transformer.apply(this); + } + + /** + * Join the termination signals from this mono and another source into the returned + * void mono + * + *

+ * + *

+ * @param other the {@link Publisher} to wait for + * complete + * @return a new combined Mono + * @see #when + */ + public final Mono and(Publisher other) { + if (this instanceof MonoWhen) { + @SuppressWarnings("unchecked") MonoWhen o = (MonoWhen) this; + Mono result = o.whenAdditionalSource(other); + if (result != null) { + return result; + } + } + + return when(this, other); + } + + /** + * Subscribe to this {@link Mono} and block indefinitely until a next signal is + * received. Returns that value, or null if the Mono completes empty. In case the Mono + * errors, the original exception is thrown (wrapped in a {@link RuntimeException} if + * it was a checked exception). + * + *

+ * + *

+ * Note that each block() will trigger a new subscription: in other words, the result + * might miss signal from hot publishers. + * + * @return T the result + */ + @Nullable + public T block() { + BlockingMonoSubscriber subscriber = new BlockingMonoSubscriber<>(); + subscribe((Subscriber) subscriber); + return subscriber.blockingGet(); + } + + /** + * Subscribe to this {@link Mono} and block until a next signal is + * received or a timeout expires. Returns that value, or null if the Mono completes + * empty. In case the Mono errors, the original exception is thrown (wrapped in a + * {@link RuntimeException} if it was a checked exception). + * If the provided timeout expires, a {@link RuntimeException} is thrown. + * + *

+ * + *

+ * Note that each block() will trigger a new subscription: in other words, the result + * might miss signal from hot publishers. + * + * @param timeout maximum time period to wait for before raising a {@link RuntimeException} + * + * @return T the result + */ + @Nullable + public T block(Duration timeout) { + BlockingMonoSubscriber subscriber = new BlockingMonoSubscriber<>(); + subscribe((Subscriber) subscriber); + return subscriber.blockingGet(timeout.toNanos(), TimeUnit.NANOSECONDS); + } + + /** + * Subscribe to this {@link Mono} and block indefinitely until a next signal is + * received or the Mono completes empty. Returns an {@link Optional}, which can be used + * to replace the empty case with an Exception via {@link Optional#orElseThrow(Supplier)}. + * In case the Mono itself errors, the original exception is thrown (wrapped in a + * {@link RuntimeException} if it was a checked exception). + * + *

+ * + *

+ * Note that each blockOptional() will trigger a new subscription: in other words, the result + * might miss signal from hot publishers. + * + * @return T the result + */ + public Optional blockOptional() { + BlockingOptionalMonoSubscriber subscriber = new BlockingOptionalMonoSubscriber<>(); + subscribe((Subscriber) subscriber); + return subscriber.blockingGet(); + } + + /** + * Subscribe to this {@link Mono} and block until a next signal is + * received, the Mono completes empty or a timeout expires. Returns an {@link Optional} + * for the first two cases, which can be used to replace the empty case with an + * Exception via {@link Optional#orElseThrow(Supplier)}. + * In case the Mono itself errors, the original exception is thrown (wrapped in a + * {@link RuntimeException} if it was a checked exception). + * If the provided timeout expires, a {@link RuntimeException} is thrown. + * + *

+ * + *

+ * Note that each block() will trigger a new subscription: in other words, the result + * might miss signal from hot publishers. + * + * @param timeout maximum time period to wait for before raising a {@link RuntimeException} + * + * @return T the result + */ + public Optional blockOptional(Duration timeout) { + BlockingOptionalMonoSubscriber subscriber = new BlockingOptionalMonoSubscriber<>(); + subscribe((Subscriber) subscriber); + return subscriber.blockingGet(timeout.toNanos(), TimeUnit.NANOSECONDS); + } + + /** + * Cast the current {@link Mono} produced type into a target produced type. + * + *

+ * + * + * @param the {@link Mono} output type + * @param clazz the target type to cast to + * + * @return a casted {@link Mono} + */ + public final Mono cast(Class clazz) { + Objects.requireNonNull(clazz, "clazz"); + return map(clazz::cast); + } + + /** + * Turn this {@link Mono} into a hot source and cache last emitted signals for further {@link Subscriber}. + * Completion and Error will also be replayed. + *

+ * + *

+ * Once the first subscription is made to this {@link Mono}, the source is subscribed to and + * the signal will be cached, indefinitely. This process cannot be cancelled. + *

+ * In the face of multiple concurrent subscriptions, this operator ensures that only one + * subscription is made to the source. + * + * @return a replaying {@link Mono} + */ + public final Mono cache() { + return onAssembly(new MonoCacheTime<>(this)); + } + + /** + * Turn this {@link Mono} into a hot source and cache last emitted signals for further + * {@link Subscriber}, with an expiry timeout. + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription. + *

+ * + *

+ * Cache loading (ie. subscription to the source) is triggered atomically by the first + * subscription to an uninitialized or expired cache, which guarantees that a single + * cache load happens at a time (and other subscriptions will get notified of the newly + * cached value when it arrives). + * + * @return a replaying {@link Mono} + */ + public final Mono cache(Duration ttl) { + return cache(ttl, Schedulers.parallel()); + } + + /** + * Turn this {@link Mono} into a hot source and cache last emitted signals for further + * {@link Subscriber}, with an expiry timeout. + *

+ * Completion and Error will also be replayed until {@code ttl} triggers in which case + * the next {@link Subscriber} will start over a new subscription. + *

+ * + *

+ * Cache loading (ie. subscription to the source) is triggered atomically by the first + * subscription to an uninitialized or expired cache, which guarantees that a single + * cache load happens at a time (and other subscriptions will get notified of the newly + * cached value when it arrives). + * + * @param ttl Time-to-live for each cached item and post termination. + * @param timer the {@link Scheduler} on which to measure the duration. + * + * @return a replaying {@link Mono} + */ + public final Mono cache(Duration ttl, Scheduler timer) { + return onAssembly(new MonoCacheTime<>(this, ttl, timer)); + } + + /** + * Turn this {@link Mono} into a hot source and cache last emitted signal for further + * {@link Subscriber}, with an expiry timeout (TTL) that depends on said signal. + * A TTL of {@link Long#MAX_VALUE} milliseconds is interpreted as indefinite caching of + * the signal (no cache cleanup is scheduled, so the signal is retained as long as this + * {@link Mono} is not garbage collected). + *

+ * Empty completion and Error will also be replayed according to their respective TTL, + * so transient errors can be "retried" by letting the {@link Function} return + * {@link Duration#ZERO}. Such a transient exception would then be propagated to the first + * subscriber but the following subscribers would trigger a new source subscription. + *

+ * Exceptions in the TTL generators themselves are processed like the {@link Duration#ZERO} + * case, except the original signal is {@link Exceptions#addSuppressed(Throwable, Throwable) suppressed} + * (in case of onError) or {@link Hooks#onNextDropped(Consumer) dropped} + * (in case of onNext). + *

+ * Note that subscribers that come in perfectly simultaneously could receive the same + * cached signal even if the TTL is set to zero. + *

+ * Cache loading (ie. subscription to the source) is triggered atomically by the first + * subscription to an uninitialized or expired cache, which guarantees that a single + * cache load happens at a time (and other subscriptions will get notified of the newly + * cached value when it arrives). + * + * @param ttlForValue the TTL-generating {@link Function} invoked when source is valued + * @param ttlForError the TTL-generating {@link Function} invoked when source is erroring + * @param ttlForEmpty the TTL-generating {@link Supplier} invoked when source is empty + * @return a replaying {@link Mono} + */ + public final Mono cache(Function ttlForValue, + Function ttlForError, + Supplier ttlForEmpty) { + return cache(ttlForValue, ttlForError, ttlForEmpty, Schedulers.parallel()); + } + + /** + * Turn this {@link Mono} into a hot source and cache last emitted signal for further + * {@link Subscriber}, with an expiry timeout (TTL) that depends on said signal. + * A TTL of {@link Long#MAX_VALUE} milliseconds is interpreted as indefinite caching of + * the signal (no cache cleanup is scheduled, so the signal is retained as long as this + * {@link Mono} is not garbage collected). + *

+ * Empty completion and Error will also be replayed according to their respective TTL, + * so transient errors can be "retried" by letting the {@link Function} return + * {@link Duration#ZERO}. Such a transient exception would then be propagated to the first + * subscriber but the following subscribers would trigger a new source subscription. + *

+ * Exceptions in the TTL generators themselves are processed like the {@link Duration#ZERO} + * case, except the original signal is {@link Exceptions#addSuppressed(Throwable, Throwable) suppressed} + * (in case of onError) or {@link Hooks#onNextDropped(Consumer) dropped} + * (in case of onNext). + *

+ * Note that subscribers that come in perfectly simultaneously could receive the same + * cached signal even if the TTL is set to zero. + *

+ * Cache loading (ie. subscription to the source) is triggered atomically by the first + * subscription to an uninitialized or expired cache, which guarantees that a single + * cache load happens at a time (and other subscriptions will get notified of the newly + * cached value when it arrives). + * + * @param ttlForValue the TTL-generating {@link Function} invoked when source is valued + * @param ttlForError the TTL-generating {@link Function} invoked when source is erroring + * @param ttlForEmpty the TTL-generating {@link Supplier} invoked when source is empty + * @param timer the {@link Scheduler} on which to measure the duration. + * @return a replaying {@link Mono} + */ + public final Mono cache(Function ttlForValue, + Function ttlForError, + Supplier ttlForEmpty, + Scheduler timer) { + return onAssembly(new MonoCacheTime<>(this, ttlForValue, ttlForError, ttlForEmpty, timer)); + } + + /** + * Cache {@link Subscriber#onNext(Object) onNext} signal received from the source and replay it to other subscribers, + * while allowing invalidation by verifying the cached value against the given {@link Predicate} each time a late + * subscription occurs. + * Note that the {@link Predicate} is only evaluated if the cache is currently populated, ie. it is not applied + * upon receiving the source {@link Subscriber#onNext(Object) onNext} signal. + * For late subscribers, if the predicate returns {@code true} the cache is invalidated and a new subscription is made + * to the source in an effort to refresh the cache with a more up-to-date value to be passed to the new subscriber. + *

+ * The predicate is not strictly evaluated once per downstream subscriber. Rather, subscriptions happening in concurrent + * batches will trigger a single evaluation of the predicate. Similarly, a batch of subscriptions happening before + * the cache is populated (ie. before this operator receives an onNext signal after an invalidation) will always + * receive the incoming value without going through the {@link Predicate}. The predicate is only triggered by + * subscribers that come in AFTER the cache is populated. Therefore, it is possible that pre-population subscribers + * receive an "invalid" value, especially if the object can switch from a valid to an invalid state in a short amount + * of time (eg. between creation, cache population and propagation to the downstream subscriber(s)). + *

+ * If the cached value needs to be discarded in case of invalidation, the recommended way is to do so in the predicate + * directly. Note that some downstream subscribers might still be using or storing the value, for example if they + * haven't requested anything yet. + *

+ * As this form of caching is explicitly value-oriented, empty source completion signals and error signals are NOT + * cached. It is always possible to use {@link #materialize()} to cache these (further using {@link #filter(Predicate)} + * if one wants to only consider empty sources or error sources). + *

+ * Predicate is applied differently depending on whether the cache is populated or not: + *

    + *
  • IF EMPTY + *
    • first incoming subscriber creates a new COORDINATOR and adds itself
    + *
  • + *
  • IF COORDINATOR + *
      + *
    1. each incoming subscriber is added to the current "batch" (COORDINATOR)
    2. + *
    3. once the value is received, the predicate is applied ONCE + *
        + *
      1. mismatch: all the batch is terminated with an error + * -> we're back to init state, next subscriber will trigger a new coordinator and a new subscription
      2. + *
      3. ok: all the batch is completed with the value -> cache is now POPULATED
      4. + *
      + *
    4. + *
    + *
  • + *
  • IF POPULATED + *
      + *
    1. each incoming subscriber causes the predicate to apply
    2. + *
    3. if ok: complete that subscriber with the value
    4. + *
    5. if mismatch, swap the current POPULATED with a new COORDINATOR and add the subscriber to that coordinator
    6. + *
    7. imagining a race between sub1 and sub2: + *
        + *
      1. OK NOK will naturally lead to sub1 completing and sub2 being put on wait inside a new COORDINATOR
      2. + *
      3. NOK NOK will race swap of POPULATED with COORDINATOR1 and COORDINATOR2 respectively + *
          + *
        1. if sub1 swaps, sub2 will dismiss the COORDINATOR2 it failed to swap and loop back, see COORDINATOR1 and add itself
        2. + *
        3. if sub2 swaps, the reverse happens
        4. + *
        5. if value is populated in the time it takes for sub2 to loop back, sub2 sees a value and triggers the predicate again (hopefully passing)
        6. + *
        + *
      4. + *
      + *
    8. + *
    + *
  • + *
+ *

+ * Cancellation is only possible for downstream subscribers when they've been added to a COORDINATOR. + * Subscribers that are received when POPULATED will either be completed right away or (if the predicate fails) end up being added to a COORDINATOR. + *

+ * When cancelling a COORDINATOR-issued subscription: + *

    + *
  1. removes itself from batch
  2. + *
  3. if 0 subscribers remaining + *
      + *
    1. swap COORDINATOR with EMPTY
    2. + *
    3. COORDINATOR cancels its source
    4. + *
    + *
  4. + *
+ *

+ * The fact that COORDINATOR cancels its source when no more subscribers remain is important, because it prevents issues with a never() source + * or a source that never produces a value passing the predicate (assuming timeouts on the subscriber). + * + * @param invalidationPredicate the {@link Predicate} used for cache invalidation. Returning {@code true} means the value is invalid and should be + * removed from the cache. + * @return a new cached {@link Mono} which can be invalidated + */ + public final Mono cacheInvalidateIf(Predicate invalidationPredicate) { + return onAssembly(new MonoCacheInvalidateIf<>(this, invalidationPredicate)); + } + + /** + * Cache {@link Subscriber#onNext(Object) onNext} signal received from the source and replay it to other subscribers, + * while allowing invalidation via a {@link Mono Mono<Void>} companion trigger generated from the currently + * cached value. + *

+ * As this form of caching is explicitly value-oriented, empty source completion signals and error signals are NOT + * cached. It is always possible to use {@link #materialize()} to cache these (further using {@link #filter(Predicate)} + * if one wants to only consider empty sources or error sources). The exception is still propagated to the subscribers + * that have accumulated between the time the source has been subscribed to and the time the onError/onComplete terminal + * signal is received. An empty source is turned into a {@link NoSuchElementException} onError. + *

+ * Completion of the trigger will invalidate the cached element, so the next subscriber that comes in will trigger + * a new subscription to the source, re-populating the cache and re-creating a new trigger out of that value. + *

+ *

    + *
  • + * If the trigger completes with an error, all registered subscribers are terminated with the same error. + *
  • + *
  • + * If all the subscribers are cancelled before the cache is populated (ie. an attempt to + * cache a {@link Mono#never()}), the source subscription is cancelled. + *
  • + *
  • + * Cancelling a downstream subscriber once the cache has been populated is not necessarily relevant, + * as the value will be immediately replayed on subscription, which usually means within onSubscribe (so + * earlier than any cancellation can happen). That said the operator will make best efforts to detect such + * cancellations and avoid propagating the value to these subscribers. + *
  • + *
+ *

+ * If the cached value needs to be discarded in case of invalidation, use the {@link #cacheInvalidateWhen(Function, Consumer)} version. + * Note that some downstream subscribers might still be using or storing the value, for example if they + * haven't requested anything yet. + *

+ * Trigger is generated only after a subscribers in the COORDINATOR have received the value, and only once. + * The only way to get out of the POPULATED state is to use the trigger, so there cannot be multiple trigger subscriptions, nor concurrent triggering. + *

+ * Cancellation is only possible for downstream subscribers when they've been added to a COORDINATOR. + * Subscribers that are received when POPULATED will either be completed right away or (if the predicate fails) end up being added to a COORDINATOR. + *

+ * When cancelling a COORDINATOR-issued subscription: + *

    + *
  1. removes itself from batch
  2. + *
  3. if 0 subscribers remaining + *
      + *
    1. swap COORDINATOR with EMPTY
    2. + *
    3. COORDINATOR cancels its source
    4. + *
    + *
  4. + *
+ *

+ * The fact that COORDINATOR cancels its source when no more subscribers remain is important, because it prevents issues with a never() source + * or a source that never produces a value passing the predicate (assuming timeouts on the subscriber). + * + * @param invalidationTriggerGenerator the {@link Function} that generates new {@link Mono Mono<Void>} triggers + * used for invalidation + * @return a new cached {@link Mono} which can be invalidated + */ + public final Mono cacheInvalidateWhen(Function> invalidationTriggerGenerator) { + return onAssembly(new MonoCacheInvalidateWhen<>(this, invalidationTriggerGenerator, null)); + } + + /** + * Cache {@link Subscriber#onNext(Object) onNext} signal received from the source and replay it to other subscribers, + * while allowing invalidation via a {@link Mono Mono<Void>} companion trigger generated from the currently + * cached value. + *

+ * As this form of caching is explicitly value-oriented, empty source completion signals and error signals are NOT + * cached. It is always possible to use {@link #materialize()} to cache these (further using {@link #filter(Predicate)} + * if one wants to only consider empty sources or error sources). The exception is still propagated to the subscribers + * that have accumulated between the time the source has been subscribed to and the time the onError/onComplete terminal + * signal is received. An empty source is turned into a {@link NoSuchElementException} onError. + *

+ * Completion of the trigger will invalidate the cached element, so the next subscriber that comes in will trigger + * a new subscription to the source, re-populating the cache and re-creating a new trigger out of that value. + *

+ *

    + *
  • + * If the trigger completes with an error, all registered subscribers are terminated with the same error. + *
  • + *
  • + * If all the subscribers are cancelled before the cache is populated (ie. an attempt to + * cache a {@link Mono#never()}), the source subscription is cancelled. + *
  • + *
  • + * Cancelling a downstream subscriber once the cache has been populated is not necessarily relevant, + * as the value will be immediately replayed on subscription, which usually means within onSubscribe (so + * earlier than any cancellation can happen). That said the operator will make best efforts to detect such + * cancellations and avoid propagating the value to these subscribers. + *
  • + *
+ *

+ * Once a cached value is invalidated, it is passed to the provided {@link Consumer} (which MUST complete normally). + * Note that some downstream subscribers might still be using or storing the value, for example if they + * haven't requested anything yet. + *

+ * Trigger is generated only after a subscribers in the COORDINATOR have received the value, and only once. + * The only way to get out of the POPULATED state is to use the trigger, so there cannot be multiple trigger subscriptions, nor concurrent triggering. + *

+ * Cancellation is only possible for downstream subscribers when they've been added to a COORDINATOR. + * Subscribers that are received when POPULATED will either be completed right away or (if the predicate fails) end up being added to a COORDINATOR. + *

+ * When cancelling a COORDINATOR-issued subscription: + *

    + *
  1. removes itself from batch
  2. + *
  3. if 0 subscribers remaining + *
      + *
    1. swap COORDINATOR with EMPTY
    2. + *
    3. COORDINATOR cancels its source
    4. + *
    + *
  4. + *
+ *

+ * The fact that COORDINATOR cancels its source when no more subscribers remain is important, because it prevents issues with a never() source + * or a source that never produces a value passing the predicate (assuming timeouts on the subscriber). + * + * @param invalidationTriggerGenerator the {@link Function} that generates new {@link Mono Mono<Void>} triggers + * used for invalidation + * @param onInvalidate the {@link Consumer} that will be applied to cached value upon invalidation + * @return a new cached {@link Mono} which can be invalidated + */ + public final Mono cacheInvalidateWhen(Function> invalidationTriggerGenerator, + Consumer onInvalidate) { + return onAssembly(new MonoCacheInvalidateWhen<>(this, invalidationTriggerGenerator, onInvalidate)); + } + + /** + * Prepare this {@link Mono} so that subscribers will cancel from it on a + * specified + * {@link Scheduler}. + * + *

+ * + * + * @param scheduler the {@link Scheduler} to signal cancel on + * + * @return a scheduled cancel {@link Mono} + */ + public final Mono cancelOn(Scheduler scheduler) { + return onAssembly(new MonoCancelOn<>(this, scheduler)); + } + + /** + * Activate traceback (full assembly tracing) for this particular {@link Mono}, in case of an error + * upstream of the checkpoint. Tracing incurs the cost of an exception stack trace + * creation. + *

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

+ * 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 + * would appear as a component of the composite. In any case, the traceback nature can be detected via + * {@link Exceptions#isTraceback(Throwable)}. + * + * @return the assembly tracing {@link Mono} + */ + public final Mono checkpoint() { + return checkpoint(null, true); + } + + /** + * Activate traceback (assembly marker) for this particular {@link Mono} by giving it a description that + * will be reflected in the assembly traceback in case of an error upstream of the + * checkpoint. Note that unlike {@link #checkpoint()}, this doesn't create a + * filled stack trace, avoiding the main cost of the operator. + * However, as a trade-off the description must be unique enough for the user to find + * out where this Mono was assembled. If you only want a generic description, and + * still rely on the stack trace to find the assembly site, use the + * {@link #checkpoint(String, boolean)} variant. + *

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

+ * 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 + * would appear as a component of the composite. In any case, the traceback nature can be detected via + * {@link Exceptions#isTraceback(Throwable)}. + * + * @param description a unique enough description to include in the light assembly traceback. + * @return the assembly marked {@link Mono} + */ + public final Mono checkpoint(String description) { + return checkpoint(Objects.requireNonNull(description), false); + } + + /** + * Activate traceback (full assembly tracing or the lighter assembly marking depending on the + * {@code forceStackTrace} option). + *

+ * By setting the {@code forceStackTrace} parameter to {@literal true}, activate assembly + * tracing for this particular {@link Mono} and give it a description that + * will be reflected in the assembly traceback in case of an error upstream of the + * checkpoint. Note that unlike {@link #checkpoint(String)}, this will incur + * the cost of an exception stack trace creation. The description could for + * example be a meaningful name for the assembled mono or a wider correlation ID, + * since the stack trace will always provide enough information to locate where this + * Flux was assembled. + *

+ * By setting {@code forceStackTrace} to {@literal false}, behaves like + * {@link #checkpoint(String)} and is subject to the same caveat in choosing the + * description. + *

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

+ * 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 + * would appear as a component of the composite. In any case, the traceback nature can be detected via + * {@link Exceptions#isTraceback(Throwable)}. + * + * @param description a description (must be unique enough if forceStackTrace is set + * to false). + * @param forceStackTrace false to make a light checkpoint without a stacktrace, true + * to use a stack trace. + * @return the assembly marked {@link Mono}. + */ + public final Mono checkpoint(@Nullable String description, boolean forceStackTrace) { + final AssemblySnapshot stacktrace; + if (!forceStackTrace) { + stacktrace = new CheckpointLightSnapshot(description); + } + else { + stacktrace = new CheckpointHeavySnapshot(description, Traces.callSiteSupplierFactory.get()); + } + + return new MonoOnAssembly<>(this, stacktrace); + } + + /** + * Concatenate emissions of this {@link Mono} with the provided {@link Publisher} + * (no interleave). + *

+ * + * + * @param other the {@link Publisher} sequence to concat after this {@link Flux} + * + * @return a concatenated {@link Flux} + */ + public final Flux concatWith(Publisher other) { + return Flux.concat(this, other); + } + + /** + * 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. + *

+ * A {@link Context} (and its {@link ContextView}) is tied to a given subscription + * and is read by querying the downstream {@link Subscriber}. {@link Subscriber} that + * don't enrich the context instead access their own downstream's context. As a result, + * this operator conceptually 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 contextToAppend the {@link ContextView} to merge with the downstream {@link Context}, + * resulting in a new more complete {@link Context} that will be visible from upstream. + * + * @return a contextualized {@link Mono} + * @see ContextView + */ + public final Mono contextWrite(ContextView contextToAppend) { + return contextWrite(c -> c.putAll(contextToAppend)); + } + + /** + * Enrich the {@link Context} visible from downstream for the benefit of upstream + * operators, by applying a {@link Function} to the downstream {@link Context}. + *

+ * The {@link Function} takes a {@link Context} for convenience, allowing to easily + * call {@link Context#put(Object, Object) write APIs} to return a new {@link Context}. + *

+ * A {@link Context} (and its {@link ContextView}) is tied to a given subscription + * and is read by querying the downstream {@link Subscriber}. {@link Subscriber} that + * don't enrich the context instead access their own downstream's context. As a result, + * this operator conceptually 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 contextModifier the {@link Function} to apply to the downstream {@link Context}, + * resulting in a new more complete {@link Context} that will be visible from upstream. + * + * @return a contextualized {@link Mono} + * @see Context + */ + public final Mono contextWrite(Function contextModifier) { + return onAssembly(new MonoContextWrite<>(this, contextModifier)); + } + + /** + * Provide a default single value if this mono is completed without any data + * + *

+ * + *

+ * @param defaultV the alternate value if this sequence is empty + * + * @return a new {@link Mono} + * + * @see Flux#defaultIfEmpty(Object) + */ + public final Mono defaultIfEmpty(T defaultV) { + if (this instanceof Fuseable.ScalarCallable) { + try { + T v = block(); + if (v == null) { + return Mono.just(defaultV); + } + } + catch (Throwable e) { + //leave MonoError returns as this + } + return this; + } + return onAssembly(new MonoDefaultIfEmpty<>(this, defaultV)); + } + + /** + * Delay this {@link Mono} element ({@link Subscriber#onNext} signal) by a given + * duration. Empty Monos or error signals are not delayed. + * + *

+ * + * + *

+ * Note that the scheduler on which the Mono chain continues execution will be the + * {@link Schedulers#parallel() parallel} scheduler if the mono is valued, or the + * current scheduler if the mono completes empty or errors. + * + * @param delay duration by which to delay the {@link Subscriber#onNext} signal + * @return a delayed {@link Mono} + */ + public final Mono delayElement(Duration delay) { + return delayElement(delay, Schedulers.parallel()); + } + + /** + * Delay this {@link Mono} element ({@link Subscriber#onNext} signal) by a given + * {@link Duration}, on a particular {@link Scheduler}. Empty monos or error signals are not delayed. + * + *

+ * + * + *

+ * Note that the scheduler on which the mono chain continues execution will be the + * scheduler provided if the mono is valued, or the current scheduler if the mono + * completes empty or errors. + * + * @param delay {@link Duration} by which to delay the {@link Subscriber#onNext} signal + * @param timer a time-capable {@link Scheduler} instance to delay the value signal on + * @return a delayed {@link Mono} + */ + public final Mono delayElement(Duration delay, Scheduler timer) { + return onAssembly(new MonoDelayElement<>(this, delay.toNanos(), TimeUnit.NANOSECONDS, timer)); + } + + /** + * Subscribe to this {@link Mono} and another {@link Publisher} that is generated from + * this Mono's element and which will be used as a trigger for relaying said element. + *

+ * That is to say, the resulting {@link Mono} delays until this Mono's element is + * emitted, generates a trigger Publisher and then delays again until the trigger + * Publisher terminates. + *

+ * Note that contiguous calls to all delayUntil are fused together. + * The triggers are generated and subscribed to in sequence, once the previous trigger + * completes. Error is propagated immediately + * downstream. In both cases, an error in the source is immediately propagated. + *

+ * + * + * @param triggerProvider a {@link Function} that maps this Mono's value into a + * {@link Publisher} whose termination will trigger relaying the value. + * + * @return this Mono, but delayed until the derived publisher terminates. + */ + public final Mono delayUntil(Function> triggerProvider) { + Objects.requireNonNull(triggerProvider, "triggerProvider required"); + if (this instanceof MonoDelayUntil) { + return ((MonoDelayUntil) this).copyWithNewTriggerGenerator(false,triggerProvider); + } + return onAssembly(new MonoDelayUntil<>(this, triggerProvider)); + } + + /** + * Delay the {@link Mono#subscribe(Subscriber) subscription} to this {@link Mono} source until the given + * period elapses. + * + *

+ * + * + * @param delay duration before subscribing this {@link Mono} + * + * @return a delayed {@link Mono} + * + */ + public final Mono delaySubscription(Duration delay) { + return delaySubscription(delay, Schedulers.parallel()); + } + + /** + * Delay the {@link Mono#subscribe(Subscriber) subscription} to this {@link Mono} source until the given + * {@link Duration} elapses. + * + *

+ * + * + * @param delay {@link Duration} before subscribing this {@link Mono} + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return a delayed {@link Mono} + * + */ + public final Mono delaySubscription(Duration delay, Scheduler timer) { + return delaySubscription(Mono.delay(delay, timer)); + } + + /** + * Delay the subscription to this {@link Mono} until another {@link Publisher} + * signals a value or completes. + * + *

+ * + * + * @param subscriptionDelay a + * {@link Publisher} to signal by next or complete this {@link Mono#subscribe(Subscriber)} + * @param the other source type + * + * @return a delayed {@link Mono} + * + */ + public final Mono delaySubscription(Publisher subscriptionDelay) { + return onAssembly(new MonoDelaySubscription<>(this, subscriptionDelay)); + } + + /** + * An operator working only if this {@link Mono} emits onNext, onError or onComplete {@link Signal} + * instances, transforming these {@link #materialize() materialized} signals into + * real signals on the {@link Subscriber}. + * The error {@link Signal} will trigger onError and complete {@link Signal} will trigger + * onComplete. + * + *

+ * + * + * @param the dematerialized type + * @return a dematerialized {@link Mono} + * @see #materialize() + */ + public final Mono dematerialize() { + @SuppressWarnings("unchecked") + Mono> thiz = (Mono>) this; + return onAssembly(new MonoDematerialize<>(thiz)); + } + + /** + * 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. + *

+ * + *

+ * The relevant signal is propagated downstream, then the {@link Runnable} is executed. + * + * @param afterTerminate the callback to call after {@link Subscriber#onComplete} or {@link Subscriber#onError} + * + * @return an observed {@link Mono} + */ + public final Mono doAfterTerminate(Runnable afterTerminate) { + Objects.requireNonNull(afterTerminate, "afterTerminate"); + return onAssembly(new MonoPeekTerminal<>(this, null, null, (s, e) -> afterTerminate.run())); + } + + /** + * Add behavior (side-effect) triggered before the {@link Mono} is + * subscribed to, which should be the first event after assembly time. + *

+ * + *

+ * Note that when several {@link #doFirst(Runnable)} operators are used anywhere in a + * chain of operators, their order of execution is reversed compared to the declaration + * order (as subscribe signal flows backward, from the ultimate subscriber to the source + * publisher): + *


+	 * Mono.just(1v)
+	 *     .doFirst(() -> System.out.println("three"))
+	 *     .doFirst(() -> System.out.println("two"))
+	 *     .doFirst(() -> System.out.println("one"));
+	 * //would print one two three
+	 * 
+	 * 
+ *

+ * In case the {@link Runnable} throws an exception, said exception will be directly + * propagated to the subscribing {@link Subscriber} along with a no-op {@link Subscription}, + * similarly to what {@link #error(Throwable)} does. Otherwise, after the handler has + * executed, the {@link Subscriber} is directly subscribed to the original source + * {@link Mono} ({@code this}). + *

+ * This side-effect method provides stronger first guarantees compared to + * {@link #doOnSubscribe(Consumer)}, which is triggered once the {@link Subscription} + * has been set up and passed to the {@link Subscriber}. + * + * @param onFirst the callback to execute before the {@link Mono} is subscribed to + * @return an observed {@link Mono} + */ + public final Mono doFirst(Runnable onFirst) { + Objects.requireNonNull(onFirst, "onFirst"); + if (this instanceof Fuseable) { + return onAssembly(new MonoDoFirstFuseable<>(this, onFirst)); + } + return onAssembly(new MonoDoFirst<>(this, onFirst)); + } + + /** + * Add behavior triggering after the {@link Mono} terminates for any reason, + * including cancellation. The terminating event ({@link SignalType#ON_COMPLETE}, + * {@link SignalType#ON_ERROR} and {@link SignalType#CANCEL}) is passed to the consumer, + * which is executed after the signal has been passed downstream. + *

+ * Note that the fact that the signal is propagated downstream before the callback is + * executed means that several doFinally in a row will be executed in + * reverse order. If you want to assert the execution of the callback + * please keep in mind that the Mono will complete before it is executed, so its + * effect might not be visible immediately after eg. a {@link #block()}. + *

+ * + * + * + * @param onFinally the callback to execute after a terminal signal (complete, error + * or cancel) + * @return an observed {@link Mono} + */ + 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)); + } + + /** + * Add behavior triggered when the {@link Mono} is cancelled. + *

+ * + *

+ * The handler is executed first, then the cancel signal is propagated upstream + * to the source. + * + * @param onCancel the callback to call on {@link Subscription#cancel()} + * + * @return a new {@link Mono} + */ + public final Mono doOnCancel(Runnable onCancel) { + Objects.requireNonNull(onCancel, "onCancel"); + return doOnSignal(this, null, null, null, onCancel); + } + + /** + * Potentially modify the behavior of the whole chain of operators upstream of this one to + * conditionally clean up elements that get discarded by these operators. + *

+ * The {@code discardHook} MUST be idempotent and safe to use on any instance of the desired + * type. + * Calls to this method are additive, and the order of invocation of the {@code discardHook} + * is the same as the order of declaration (calling {@code .filter(...).doOnDiscard(first).doOnDiscard(second)} + * will let the filter invoke {@code first} then {@code second} handlers). + *

+ * Two main categories of discarding operators exist: + *

    + *
  • filtering operators, dropping some source elements as part of their designed behavior
  • + *
  • operators that prefetch a few elements and keep them around pending a request, but get cancelled/in error
  • + *
+ * WARNING: Not all operators support this instruction. The ones that do are identified in the javadoc by + * the presence of a Discard Support section. + * + * @param type the {@link Class} of elements in the upstream chain of operators that + * this cleanup hook should take into account. + * @param discardHook a {@link Consumer} of elements in the upstream chain of operators + * that performs the cleanup. + * @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)); + } + + /** + * Add behavior triggered when the {@link Mono} emits a data successfully. + * + *

+ * + *

+ * The {@link Consumer} is executed first, then the onNext signal is propagated + * downstream. + * + * @param onNext the callback to call on {@link Subscriber#onNext} + * + * @return a new {@link Mono} + */ + public final Mono doOnNext(Consumer onNext) { + Objects.requireNonNull(onNext, "onNext"); + return doOnSignal(this, null, onNext, null, null); + } + + /** + * Add behavior triggered as soon as the {@link Mono} can be considered to have completed successfully. + * The value passed to the {@link Consumer} reflects the type of completion: + * + *

    + *
  • null : completed without data. handler is executed right before onComplete is propagated downstream
  • + *
  • T: completed with data. handler is executed right before onNext is propagated downstream
  • + *
+ * + *

+ * + *

+ * The {@link Consumer} is executed before propagating either onNext or onComplete downstream. + * + * @param onSuccess the callback to call on, argument is null if the {@link Mono} + * completes without data + * {@link Subscriber#onNext} or {@link Subscriber#onComplete} without preceding {@link Subscriber#onNext} + * + * @return a new {@link Mono} + */ + public final Mono doOnSuccess(Consumer onSuccess) { + Objects.requireNonNull(onSuccess, "onSuccess"); + return doOnTerminalSignal(this, onSuccess, null, null); + } + + /** + * Add behavior triggered when the {@link Mono} emits an item, fails with an error + * or completes successfully. All these events are represented as a {@link Signal} + * that is passed to the side-effect callback. Note that this is an advanced operator, + * typically used for monitoring of a Mono. + * These {@link Signal} have a {@link Context} associated to them. + *

+ * + *

+ * The {@link Consumer} is executed first, then the relevant signal is propagated + * downstream. + * + * @param signalConsumer the mandatory callback to call on + * {@link Subscriber#onNext(Object)}, {@link Subscriber#onError(Throwable)} and + * {@link Subscriber#onComplete()} + * @return an observed {@link Mono} + * @see #doOnNext(Consumer) + * @see #doOnError(Consumer) + * @see #materialize() + * @see Signal + */ + public final Mono doOnEach(Consumer> signalConsumer) { + Objects.requireNonNull(signalConsumer, "signalConsumer"); + if (this instanceof Fuseable) { + return onAssembly(new MonoDoOnEachFuseable<>(this, signalConsumer)); + } + return onAssembly(new MonoDoOnEach<>(this, signalConsumer)); + + } + + /** + * Add behavior triggered when the {@link Mono} completes with an error. + * + *

+ * + *

+ * The {@link Consumer} is executed first, then the onError signal is propagated + * downstream. + * + * @param onError the error callback to call on {@link Subscriber#onError(Throwable)} + * + * @return a new {@link Mono} + */ + public final Mono doOnError(Consumer onError) { + Objects.requireNonNull(onError, "onError"); + return doOnTerminalSignal(this, null, onError, null); + } + + + /** + * Add behavior triggered when the {@link Mono} completes with an error matching the given exception type. + *

+ * + *

+ * The {@link Consumer} is executed first, then the onError signal is propagated + * downstream. + * + * @param exceptionType the type of exceptions to handle + * @param onError the error handler for relevant errors + * @param type of the error to handle + * + * @return an observed {@link Mono} + * + */ + public final Mono doOnError(Class exceptionType, + final Consumer onError) { + Objects.requireNonNull(exceptionType, "type"); + Objects.requireNonNull(onError, "onError"); + return doOnTerminalSignal(this, null, + error -> { + if (exceptionType.isInstance(error)) onError.accept(exceptionType.cast(error)); + }, + null); + } + + /** + * Add behavior triggered when the {@link Mono} completes with an error matching the given predicate. + *

+ * + *

+ * The {@link Consumer} is executed first, then the onError signal is propagated + * downstream. + * + * @param predicate the matcher for exceptions to handle + * @param onError the error handler for relevant error + * + * @return an observed {@link Mono} + * + */ + public final Mono doOnError(Predicate predicate, + final Consumer onError) { + Objects.requireNonNull(predicate, "predicate"); + Objects.requireNonNull(onError, "onError"); + return doOnTerminalSignal(this, null, + error -> { + if (predicate.test(error)) onError.accept(error); + }, + null); + } + /** + * Add behavior triggering a {@link LongConsumer} when the {@link Mono} receives any request. + *

+ * Note that non fatal error raised in the callback will not be propagated and + * will simply trigger {@link Operators#onOperatorError(Throwable, Context)}. + * + *

+ * + *

+ * The {@link LongConsumer} is executed first, then the request signal is propagated + * upstream to the parent. + * + * @param consumer the consumer to invoke on each request + * + * @return an observed {@link Mono} + */ + public final Mono doOnRequest(final LongConsumer consumer) { + Objects.requireNonNull(consumer, "consumer"); + return doOnSignal(this, null, null, consumer, null); + } + + /** + * Add behavior (side-effect) triggered when the {@link Mono} is being subscribed, + * that is to say when a {@link Subscription} has been produced by the {@link Publisher} + * and is being passed to the {@link Subscriber#onSubscribe(Subscription)}. + *

+ * This method is not intended for capturing the subscription and calling its methods, + * but for side effects like monitoring. For instance, the correct way to cancel a subscription is + * to call {@link Disposable#dispose()} on the Disposable returned by {@link Mono#subscribe()}. + *

+ * + *

+ * The {@link Consumer} is executed first, then the {@link Subscription} is propagated + * downstream to the next subscriber in the chain that is being established. + * + * @param onSubscribe the callback to call on {@link Subscriber#onSubscribe(Subscription)} + * + * @return a new {@link Mono} + * @see #doFirst(Runnable) + */ + public final Mono doOnSubscribe(Consumer onSubscribe) { + Objects.requireNonNull(onSubscribe, "onSubscribe"); + return doOnSignal(this, onSubscribe, null, null, null); + } + + /** + * 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 + * completion, so the handler is invoked BEFORE the element is propagated (same as with {@link #doOnSuccess(Consumer)}). + * + *

+ *

+ * The {@link Runnable} is executed first, then the onNext/onComplete/onError signal is propagated + * downstream. + * + * @param onTerminate the callback to call {@link Subscriber#onNext}, {@link Subscriber#onComplete} without preceding {@link Subscriber#onNext} or {@link Subscriber#onError} + * + * @return a new {@link Mono} + */ + public final Mono doOnTerminate(Runnable onTerminate) { + Objects.requireNonNull(onTerminate, "onTerminate"); + return doOnTerminalSignal(this, ignoreValue -> onTerminate.run(), ignoreError -> onTerminate.run(), null); + } + + /** + * Map this {@link Mono} into {@link reactor.util.function.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. + * + *

+ * + * + * @return a new {@link Mono} that emits a tuple of time elapsed in milliseconds and matching data + * @see #timed() + */ + public final Mono> elapsed() { + return elapsed(Schedulers.parallel()); + } + + /** + * Map this {@link Mono} sequence into {@link reactor.util.function.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}. + * + *

+ * + * + * @param scheduler a {@link Scheduler} instance to read time from + * @return a new {@link Mono} that emits a tuple of time elapsed in milliseconds and matching data + * @see #timed(Scheduler) + */ + public final Mono> elapsed(Scheduler scheduler) { + Objects.requireNonNull(scheduler, "scheduler"); + return onAssembly(new MonoElapsed<>(this, scheduler)); + } + + /** + * Recursively expand elements into a graph and emit all the resulting element, + * in a depth-first traversal order. + *

+ * That is: emit the value from this {@link Mono}, expand it and emit the first value + * at this first level of recursion, and so on... When no more recursion is possible, + * backtrack to the previous level and re-apply the strategy. + *

+ * For example, given the hierarchical structure + *

+	 *  A
+	 *   - AA
+	 *     - aa1
+	 *   - AB
+	 *     - ab1
+	 *   - a1
+	 * 
+ * + * Expands {@code Mono.just(A)} into + *
+	 *  A
+	 *  AA
+	 *  aa1
+	 *  AB
+	 *  ab1
+	 *  a1
+	 * 
+ * + * @param expander the {@link Function} applied at each level of recursion to expand + * values into a {@link Publisher}, producing a graph. + * @param capacityHint a capacity hint to prepare the inner queues to accommodate n + * elements per level of recursion. + * + * @return this Mono expanded depth-first to a {@link Flux} + */ + public final Flux expandDeep(Function> expander, + int capacityHint) { + return Flux.onAssembly(new MonoExpand<>(this, expander, false, capacityHint)); + } + + /** + * Recursively expand elements into a graph and emit all the resulting element, + * in a depth-first traversal order. + *

+ * That is: emit the value from this {@link Mono}, expand it and emit the first value + * at this first level of recursion, and so on... When no more recursion is possible, + * backtrack to the previous level and re-apply the strategy. + *

+ * For example, given the hierarchical structure + *

+	 *  A
+	 *   - AA
+	 *     - aa1
+	 *   - AB
+	 *     - ab1
+	 *   - a1
+	 * 
+ * + * Expands {@code Mono.just(A)} into + *
+	 *  A
+	 *  AA
+	 *  aa1
+	 *  AB
+	 *  ab1
+	 *  a1
+	 * 
+ * + * @param expander the {@link Function} applied at each level of recursion to expand + * values into a {@link Publisher}, producing a graph. + * + * @return this Mono expanded depth-first to a {@link Flux} + */ + public final Flux expandDeep(Function> expander) { + return expandDeep(expander, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Recursively expand elements into a graph and emit all the resulting element using + * a breadth-first traversal strategy. + *

+ * That is: emit the value from this {@link Mono} first, then expand it 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... + *

+ * For example, given the hierarchical structure + *

+	 *  A
+	 *   - AA
+	 *     - aa1
+	 *   - AB
+	 *     - ab1
+	 *   - a1
+	 * 
+ * + * Expands {@code Mono.just(A)} into + *
+	 *  A
+	 *  AA
+	 *  AB
+	 *  a1
+	 *  aa1
+	 *  ab1
+	 * 
+ * + * @param expander the {@link Function} applied at each level of recursion to expand + * values into a {@link Publisher}, producing a graph. + * @param capacityHint a capacity hint to prepare the inner queues to accommodate n + * elements per level of recursion. + * + * @return this Mono expanded breadth-first to a {@link Flux} + */ + public final Flux expand(Function> expander, + int capacityHint) { + return Flux.onAssembly(new MonoExpand<>(this, expander, true, capacityHint)); + } + + /** + * Recursively expand elements into a graph and emit all the resulting element using + * a breadth-first traversal strategy. + *

+ * That is: emit the value from this {@link Mono} first, then expand it 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... + *

+ * For example, given the hierarchical structure + *

+	 *  A
+	 *   - AA
+	 *     - aa1
+	 *   - AB
+	 *     - ab1
+	 *   - a1
+	 * 
+ * + * Expands {@code Mono.just(A)} into + *
+	 *  A
+	 *  AA
+	 *  AB
+	 *  a1
+	 *  aa1
+	 *  ab1
+	 * 
+ * + * @param expander the {@link Function} applied at each level of recursion to expand + * values into a {@link Publisher}, producing a graph. + * + * @return this Mono expanded breadth-first to a {@link Flux} + */ + public final Flux expand(Function> expander) { + return expand(expander, Queues.SMALL_BUFFER_SIZE); + } + + /** + * If this {@link Mono} is valued, test the result and replay it if predicate returns true. + * Otherwise complete without value. + * + *

+ * + * + *

Discard Support: This operator discards the element if it does not match the filter. It + * also discards upon cancellation or error triggered by a data signal. + * + * @param tester the predicate to evaluate + * + * @return a filtered {@link Mono} + */ + public final Mono filter(final Predicate tester) { + if (this instanceof Fuseable) { + return onAssembly(new MonoFilterFuseable<>(this, tester)); + } + return onAssembly(new MonoFilter<>(this, tester)); + } + + /** + * If this {@link Mono} is valued, test the value asynchronously using a generated + * {@code Publisher} test. The value from the Mono is replayed if the + * first item emitted by the test is {@literal true}. It is dropped if the test is + * either empty or its first emitted value is {@literal false}. + *

+ * Note that only the first value of the test publisher is considered, and unless it + * is a {@link Mono}, test will be cancelled after receiving that first value. + * + *

+ * + * + *

Discard Support: This operator discards the element if it does not match the filter. It + * also discards upon cancellation or error triggered by a data signal. + * + * @param asyncPredicate the function generating a {@link Publisher} of {@link Boolean} + * to filter the Mono with + * + * @return a filtered {@link Mono} + */ + public final Mono filterWhen(Function> asyncPredicate) { + return onAssembly(new MonoFilterWhen<>(this, asyncPredicate)); + } + + /** + * Transform the item emitted by this {@link Mono} asynchronously, returning the + * value emitted by another {@link Mono} (possibly changing the value type). + * + *

+ * + * + * @param transformer the function to dynamically bind a new {@link Mono} + * @param the result type bound + * + * @return a new {@link Mono} with an asynchronously mapped value. + */ + public final Mono flatMap(Function> + transformer) { + return onAssembly(new MonoFlatMap<>(this, transformer)); + } + + /** + * Transform the item emitted by this {@link Mono} into a Publisher, then forward + * its emissions into the returned {@link Flux}. + * + *

+ * + * + * @param mapper the + * {@link Function} to produce a sequence of R from the eventual passed {@link Subscriber#onNext} + * @param the merged sequence type + * + * @return a new {@link Flux} as the sequence is not guaranteed to be single at most + */ + public final Flux flatMapMany(Function> mapper) { + return Flux.onAssembly(new MonoFlatMapMany<>(this, mapper)); + } + + /** + * Transform the signals emitted by this {@link Mono} into signal-specific Publishers, + * then forward the applicable Publisher's emissions into the returned {@link Flux}. + * + *

+ * + * + * @param mapperOnNext the {@link Function} to call on next data and returning a sequence to merge + * @param mapperOnError the {@link Function} to call on error signal and returning a sequence to merge + * @param mapperOnComplete the {@link Function} to call on complete signal and returning a sequence to merge + * @param the type of the produced inner sequence + * + * @return a new {@link Flux} as the sequence is not guaranteed to be single at most + * + * @see Flux#flatMap(Function, Function, Supplier) + */ + public final Flux flatMapMany(Function> mapperOnNext, + Function> mapperOnError, + Supplier> mapperOnComplete) { + return flux().flatMap(mapperOnNext, mapperOnError, mapperOnComplete); + } + + /** + * Transform the item emitted by this {@link Mono} into {@link Iterable}, then forward + * its elements into the returned {@link Flux}. + * The {@link Iterable#iterator()} method will be called at least once and at most twice. + * + *

+ * + *

+ * This operator inspects each {@link Iterable}'s {@link Spliterator} to assess if the iteration + * can be guaranteed to be finite (see {@link Operators#onDiscardMultiple(Iterator, boolean, Context)}). + * Since the default Spliterator wraps the Iterator we can have two {@link Iterable#iterator()} + * calls per iterable. This second invocation is skipped on a {@link Collection } however, a type which is + * assumed to be always finite. + * + *

Discard Support: Upon cancellation, this operator discards {@code T} elements it prefetched and, in + * some cases, attempts to discard remainder of the currently processed {@link Iterable} (if it can + * safely ensure the iterator is finite). Note that this means each {@link Iterable}'s {@link Iterable#iterator()} + * method could be invoked twice. + * + * @param mapper the {@link Function} to transform input item into a sequence {@link Iterable} + * @param the merged output sequence type + * + * @return a merged {@link Flux} + * + */ + public final Flux flatMapIterable(Function> mapper) { + return Flux.onAssembly(new MonoFlattenIterable<>(this, mapper, Integer + .MAX_VALUE, Queues.one())); + } + + /** + * Convert this {@link Mono} to a {@link Flux} + * + * @return a {@link Flux} variant of this {@link Mono} + */ + public final Flux flux() { + if (this instanceof Callable && !(this instanceof Fuseable.ScalarCallable)) { + @SuppressWarnings("unchecked") Callable thiz = (Callable) this; + return Flux.onAssembly(new FluxCallable<>(thiz)); + } + return Flux.from(this); + } + + /** + * Emit a single boolean true if this {@link Mono} has an element. + * + *

+ * + * + * @return a new {@link Mono} with true if a value is emitted and false + * otherwise + */ + public final Mono hasElement() { + return onAssembly(new MonoHasElement<>(this)); + } + + /** + * Handle the items emitted by this {@link Mono} by calling a biconsumer with the + * 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()}. + * + * @param handler the handling {@link BiConsumer} + * @param the transformed type + * + * @return a transformed {@link Mono} + */ + public final Mono handle(BiConsumer> handler) { + if (this instanceof Fuseable) { + return onAssembly(new MonoHandleFuseable<>(this, handler)); + } + return onAssembly(new MonoHandle<>(this, handler)); + } + + /** + * Hides the identity of this {@link Mono} instance. + * + *

The main purpose of this operator is to prevent certain identity-based + * optimizations from happening, mostly for diagnostic purposes. + * + * @return a new {@link Mono} preventing {@link Publisher} / {@link Subscription} based Reactor optimizations + */ + public final Mono hide() { + return onAssembly(new MonoHide<>(this)); + } + + /** + * Ignores onNext signal (dropping it) and only propagates termination events. + * + *

+ * + *

+ * + *

Discard Support: This operator discards the source element. + * + * @return a new empty {@link Mono} representing the completion of this {@link Mono}. + */ + public final Mono ignoreElement() { + return onAssembly(new MonoIgnoreElement<>(this)); + } + + /** + * Observe all Reactive Streams signals and trace them using {@link Logger} support. + * Default will use {@link Level#INFO} and {@code java.util.logging}. + * If SLF4J is available, it will be used instead. + * + *

+ * + *

+ * The default log category will be "reactor.Mono", followed by a suffix generated from + * the source operator, e.g. "reactor.Mono.Map". + * + * @return a new {@link Mono} that logs signals + * + * @see Flux#log() + */ + public final Mono log() { + return log(null, Level.INFO); + } + + /** + * Observe all Reactive Streams signals and use {@link Logger} support to handle trace implementation. Default will + * use {@link Level#INFO} and java.util.logging. If SLF4J is available, it will be used instead. + * + *

+ * + * + * @param category to be mapped into logger configuration (e.g. org.springframework + * .reactor). If category ends with "." like "reactor.", a generated operator + * suffix will complete, e.g. "reactor.Flux.Map". + * + * @return a new {@link Mono} + */ + public final Mono log(@Nullable String category) { + return log(category, Level.INFO); + } + + /** + * Observe Reactive Streams signals matching the passed flags {@code options} and use + * {@link Logger} support to handle trace implementation. Default will use the passed + * {@link Level} and java.util.logging. If SLF4J is available, it will be used instead. + * + * Options allow fine grained filtering of the traced signal, for instance to only capture onNext and onError: + *

+	 *     mono.log("category", SignalType.ON_NEXT, SignalType.ON_ERROR)
+	 * 
+ *

+ * + * + * @param category to be mapped into logger configuration (e.g. org.springframework + * .reactor). If category ends with "." like "reactor.", a generated operator + * suffix will complete, e.g. "reactor.Flux.Map". + * @param level the {@link Level} to enforce for this tracing Mono (only FINEST, FINE, + * INFO, WARNING and SEVERE are taken into account) + * @param options a vararg {@link SignalType} option to filter log messages + * + * @return a new {@link Mono} + * + */ + public final Mono log(@Nullable String category, Level level, SignalType... options) { + return log(category, level, false, options); + } + + /** + * Observe Reactive Streams signals matching the passed filter {@code options} and + * use {@link Logger} support to + * handle trace + * implementation. Default will + * use the passed {@link Level} and java.util.logging. If SLF4J is available, it will be used instead. + * + * Options allow fine grained filtering of the traced signal, for instance to only capture onNext and onError: + *

+	 *     mono.log("category", Level.INFO, SignalType.ON_NEXT, SignalType.ON_ERROR)
+	 * 
+ *

+ * + * + * @param category to be mapped into logger configuration (e.g. org.springframework + * .reactor). If category ends with "." like "reactor.", a generated operator + * suffix will complete, e.g. "reactor.Mono.Map". + * @param level the {@link Level} to enforce for this tracing Mono (only FINEST, FINE, + * INFO, WARNING and SEVERE are taken into account) + * @param showOperatorLine capture the current stack to display operator + * class/line number. + * @param options a vararg {@link SignalType} option to filter log messages + * + * @return a new unaltered {@link Mono} + */ + public final Mono log(@Nullable String category, + Level level, + boolean showOperatorLine, + SignalType... options) { + SignalLogger log = new SignalLogger<>(this, category, level, + showOperatorLine, options); + + if (this instanceof Fuseable) { + return onAssembly(new MonoLogFuseable<>(this, log)); + } + return onAssembly(new MonoLog<>(this, log)); + } + + + /** + * Observe Reactive Streams signals matching the passed filter {@code options} and + * trace them using a specific user-provided {@link Logger}, at {@link Level#INFO} level. + *

+ * + * + * @param logger the {@link Logger} to use, instead of resolving one through a category. + * + * @return a new {@link Mono} that logs signals + */ + public final Mono log(Logger logger) { + return log(logger, Level.INFO, false); + } + + /** + * Observe Reactive Streams signals matching the passed filter {@code options} and + * trace them using a specific user-provided {@link Logger}, at the given {@link Level}. + *

+ * Options allow fine grained filtering of the traced signal, for instance to only + * capture onNext and onError: + *

+	 *     flux.log(myCustomLogger, Level.INFO, SignalType.ON_NEXT, SignalType.ON_ERROR)
+	 * 
+ *

+ * + * + * @param logger the {@link Logger} to use, instead of resolving one through a category. + * @param level the {@link Level} to enforce for this tracing Flux (only FINEST, FINE, + * INFO, WARNING and SEVERE are taken into account) + * @param showOperatorLine capture the current stack to display operator class/line number. + * @param options a vararg {@link SignalType} option to filter log messages + * + * @return a new {@link Mono} that logs signals + */ + public final Mono log(Logger logger, + Level level, + boolean showOperatorLine, + SignalType... options) { + SignalLogger log = new SignalLogger<>(this, "IGNORED", level, + showOperatorLine, + s -> logger, + options); + + if (this instanceof Fuseable) { + return onAssembly(new MonoLogFuseable<>(this, log)); + } + return onAssembly(new MonoLog<>(this, log)); + } + + /** + * Transform the item emitted by this {@link Mono} by applying a synchronous function to it. + * + *

+ * + * + * @param mapper the synchronous transforming {@link Function} + * @param the transformed type + * + * @return a new {@link Mono} + */ + public final Mono map(Function mapper) { + if (this instanceof Fuseable) { + return onAssembly(new MonoMapFuseable<>(this, mapper)); + } + return onAssembly(new MonoMap<>(this, mapper)); + } + + /** + * Transform the item emitted by this {@link Mono} by applying a synchronous function to it, which is allowed + * to produce a {@code null} value. In that case, the resulting Mono completes immediately. + * This operator effectively behaves like {@link #map(Function)} followed by {@link #filter(Predicate)} + * although {@code null} is not a supported value, so it can't be filtered out. + * + *

+ * + * + * @param mapper the synchronous transforming {@link Function} + * @param the transformed type + * + * @return a new {@link Mono} + */ + public final Mono mapNotNull(Function mapper) { + return this.handle((t, sink) -> { + R r = mapper.apply(t); + if (r != null) { + sink.next(r); + } + }); + } + + /** + * Transform incoming onNext, onError and onComplete signals into {@link Signal} instances, + * materializing these signals. + * Since the error is materialized as a {@code Signal}, the propagation will be stopped and onComplete will be + * emitted. Complete signal will first emit a {@code Signal.complete()} and then effectively complete the flux. + * All these {@link Signal} have a {@link Context} associated to them. + *

+ * + * + * @return a {@link Mono} of materialized {@link Signal} + * @see #dematerialize() + */ + public final Mono> materialize() { + return onAssembly(new MonoMaterialize<>(this)); + } + + /** + * Merge emissions of this {@link Mono} with the provided {@link Publisher}. + * The element from the Mono may be interleaved with the elements of the Publisher. + * + *

+ * + * + * @param other the {@link Publisher} to merge with + * + * @return a new {@link Flux} as the sequence is not guaranteed to be at most 1 + */ + public final Flux mergeWith(Publisher other) { + return Flux.merge(this, other); + } + + /** + * Activate metrics for this sequence, provided there is an instrumentation facade + * on the classpath (otherwise this method is a pure no-op). + *

+ * Metrics are gathered on {@link Subscriber} events, and it is recommended to also + * {@link #name(String) name} (and optionally {@link #tag(String, String) tag}) the + * sequence. + *

+ * 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)} + * prior to using this operator, the default being + * {@link io.micrometer.core.instrument.Metrics#globalRegistry}. + *

+ * + * @return an instrumented {@link Mono} + * + * @see #name(String) + * @see #tag(String, String) + */ + public final Mono metrics() { + if (!Metrics.isInstrumentationAvailable()) { + return this; + } + + if (this instanceof Fuseable) { + return onAssembly(new MonoMetricsFuseable<>(this)); + } + return onAssembly(new MonoMetrics<>(this)); + } + + /** + * 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. + * + * @param name a name for the sequence + * + * @return the same sequence, but bearing a name + * + * @see #metrics() + * @see #tag(String, String) + */ + public final Mono name(String name) { + return MonoName.createOrAppend(this, name); + } + + /** + * Emit the first available signal from this mono or the other mono. + * + *

+ * + * + * @param other the racing other {@link Mono} to compete with for the signal + * + * @return a new {@link Mono} + * @see #firstWithSignal + */ + public final Mono or(Mono other) { + if (this instanceof MonoFirstWithSignal) { + MonoFirstWithSignal a = (MonoFirstWithSignal) this; + Mono result = a.orAdditionalSource(other); + if (result != null) { + return result; + } + } + return firstWithSignal(this, other); + } + + /** + * Evaluate the emitted value against the given {@link Class} type. If the + * value matches the type, it is passed into the new {@link Mono}. Otherwise the + * value is ignored. + * + *

+ * + * + * @param clazz the {@link Class} type to test values against + * + * @return a new {@link Mono} filtered on the requested type + */ + public final Mono ofType(final Class clazz) { + Objects.requireNonNull(clazz, "clazz"); + return filter(o -> clazz.isAssignableFrom(o.getClass())).cast(clazz); + } + + /** + * 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}. + * Alternatively, throwing from that biconsumer will propagate the thrown exception downstream + * in place of the original error, which is added as a suppressed exception to the new one. + *

+ * This operator is offered on {@link Mono} mainly as a way to propagate the configuration to + * upstream {@link Flux}. The mode doesn't really make sense on a {@link Mono}, since we're sure + * there will be no further value to continue with. + * {@link #onErrorResume(Function)} is a more classical fit. + *

+ * Note that onErrorContinue() is a specialist operator that can make the behaviour of your + * reactive chain unclear. It operates on upstream, not downstream operators, it requires specific + * operator support to work, and the scope can easily propagate upstream into library code + * that didn't anticipate it (resulting in unintended behaviour.) + *

+ * In most cases, you should instead handle the error inside the specific function which may cause + * it. Specifically, on each inner publisher you can use {@code doOnError} to log the error, and + * {@code onErrorResume(e -> Mono.empty())} to drop erroneous elements: + *

+ *

+	 * .flatMap(id -> repository.retrieveById(id)
+	 *                          .doOnError(System.err::println)
+	 *                          .onErrorResume(e -> Mono.empty()))
+	 * 
+ *

+ * This has the advantage of being much clearer, has no ambiguity with regards to operator support, + * and cannot leak upstream. + * + * @param errorConsumer a {@link BiConsumer} fed with errors matching the {@link Class} + * and the value that triggered the error. + * @return a {@link Mono} that attempts to continue processing on errors. + */ + public final Mono onErrorContinue(BiConsumer errorConsumer) { + BiConsumer genericConsumer = errorConsumer; + return subscriberContext(Context.of( + OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, + OnNextFailureStrategy.resume(genericConsumer) + )); + } + + /** + * Let compatible operators upstream recover from errors by dropping the + * incriminating element from the sequence and continuing with subsequent elements. + * Only errors matching the specified {@code type} are recovered from. + * The recovered error and associated value are notified via the provided {@link BiConsumer}. + * Alternatively, throwing from that biconsumer will propagate the thrown exception downstream + * in place of the original error, which is added as a suppressed exception to the new one. + *

+ * This operator is offered on {@link Mono} mainly as a way to propagate the configuration to + * upstream {@link Flux}. The mode doesn't really make sense on a {@link Mono}, since we're sure + * there will be no further value to continue with. + * {@link #onErrorResume(Function)} is a more classical fit. + *

+ * Note that onErrorContinue() is a specialist operator that can make the behaviour of your + * reactive chain unclear. It operates on upstream, not downstream operators, it requires specific + * operator support to work, and the scope can easily propagate upstream into library code + * that didn't anticipate it (resulting in unintended behaviour.) + *

+ * In most cases, you should instead handle the error inside the specific function which may cause + * it. Specifically, on each inner publisher you can use {@code doOnError} to log the error, and + * {@code onErrorResume(e -> Mono.empty())} to drop erroneous elements: + *

+ *

+	 * .flatMap(id -> repository.retrieveById(id)
+	 *                          .doOnError(MyException.class, System.err::println)
+	 *                          .onErrorResume(MyException.class, e -> Mono.empty()))
+	 * 
+ *

+ * This has the advantage of being much clearer, has no ambiguity with regards to operator support, + * and cannot leak upstream. + * + * @param type the {@link Class} of {@link Exception} that are resumed from. + * @param errorConsumer a {@link BiConsumer} fed with errors matching the {@link Class} + * and the value that triggered the error. + * @return a {@link Mono} that attempts to continue processing on some errors. + */ + public final Mono onErrorContinue(Class type, BiConsumer errorConsumer) { + return onErrorContinue(type::isInstance, errorConsumer); + } + + /** + * Let compatible operators upstream recover from errors by dropping the + * incriminating element from the sequence and continuing with subsequent elements. + * Only errors matching the {@link Predicate} are recovered from (note that this + * predicate can be applied several times and thus must be idempotent). + * The recovered error and associated value are notified via the provided {@link BiConsumer}. + * Alternatively, throwing from that biconsumer will propagate the thrown exception downstream + * in place of the original error, which is added as a suppressed exception to the new one. + *

+ * This operator is offered on {@link Mono} mainly as a way to propagate the configuration to + * upstream {@link Flux}. The mode doesn't really make sense on a {@link Mono}, since we're sure + * there will be no further value to continue with. + * {@link #onErrorResume(Function)} is a more classical fit. + *

+ * Note that onErrorContinue() is a specialist operator that can make the behaviour of your + * reactive chain unclear. It operates on upstream, not downstream operators, it requires specific + * operator support to work, and the scope can easily propagate upstream into library code + * that didn't anticipate it (resulting in unintended behaviour.) + *

+ * In most cases, you should instead handle the error inside the specific function which may cause + * it. Specifically, on each inner publisher you can use {@code doOnError} to log the error, and + * {@code onErrorResume(e -> Mono.empty())} to drop erroneous elements: + *

+ *

+	 * .flatMap(id -> repository.retrieveById(id)
+	 *                          .doOnError(errorPredicate, System.err::println)
+	 *                          .onErrorResume(errorPredicate, e -> Mono.empty()))
+	 * 
+ *

+ * This has the advantage of being much clearer, has no ambiguity with regards to operator support, + * and cannot leak upstream. + * + * @param errorPredicate a {@link Predicate} used to filter which errors should be resumed from. + * This MUST be idempotent, as it can be used several times. + * @param errorConsumer a {@link BiConsumer} fed with errors matching the predicate and the value + * that triggered the error. + * @return a {@link Mono} that attempts to continue processing on some errors. + */ + public final Mono onErrorContinue(Predicate errorPredicate, + BiConsumer errorConsumer) { + //this cast is ok as only T values will be propagated in this sequence + @SuppressWarnings("unchecked") + Predicate genericPredicate = (Predicate) errorPredicate; + BiConsumer genericErrorConsumer = errorConsumer; + return subscriberContext(Context.of( + OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, + OnNextFailureStrategy.resumeIf(genericPredicate, genericErrorConsumer) + )); + } + + /** + * If an {@link #onErrorContinue(BiConsumer)} variant has been used downstream, reverts + * to the default 'STOP' mode where errors are terminal events upstream. It can be + * used for easier scoping of the on next failure strategy or to override the + * inherited strategy in a sub-stream (for example in a flatMap). It has no effect if + * {@link #onErrorContinue(BiConsumer)} has not been used downstream. + * + * @return a {@link Mono} that terminates on errors, even if {@link #onErrorContinue(BiConsumer)} + * was used downstream + */ + public final Mono onErrorStop() { + return subscriberContext(Context.of( + OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, + OnNextFailureStrategy.stop())); + } + + /** + * Transform an error emitted by this {@link Mono} by synchronously applying a function + * to it if the error matches the given predicate. Otherwise let the error pass through. + *

+ * + * + * @param predicate the error predicate + * @param mapper the error transforming {@link Function} + * + * @return a {@link Mono} that transforms some source errors to other errors + */ + public final Mono onErrorMap(Predicate predicate, + Function mapper) { + return onErrorResume(predicate, e -> Mono.error(mapper.apply(e))); + } + + + /** + * Transform any error emitted by this {@link Mono} by synchronously applying a function to it. + *

+ * + * + * @param mapper the error transforming {@link Function} + * + * @return a {@link Mono} that transforms source errors to other errors + */ + public final Mono onErrorMap(Function mapper) { + return onErrorResume(e -> Mono.error(mapper.apply(e))); + } + + /** + * Transform an error emitted by this {@link Mono} by synchronously applying a function + * to it if the error matches the given type. Otherwise let the error pass through. + *

+ * + * + * @param type the class of the exception type to react to + * @param mapper the error transforming {@link Function} + * @param the error type + * + * @return a {@link Mono} that transforms some source errors to other errors + */ + public final Mono onErrorMap(Class type, + Function mapper) { + @SuppressWarnings("unchecked") + Function handler = (Function)mapper; + return onErrorMap(type::isInstance, handler); + } + + /** + * Subscribe to a fallback publisher when any error occurs, using a function to + * choose the fallback depending on the error. + * + *

+ * + * + * @param fallback the function to choose the fallback to an alternative {@link Mono} + * + * @return a {@link Mono} falling back upon source onError + * + * @see Flux#onErrorResume + */ + public final Mono onErrorResume(Function> fallback) { + return onAssembly(new MonoOnErrorResume<>(this, fallback)); + } + + /** + * Subscribe to a fallback publisher when an error matching the given type + * occurs, using a function to choose the fallback depending on the error. + *

+ * + * + * @param type the error type to match + * @param fallback the function to choose the fallback to an alternative {@link Mono} + * @param the error type + * + * @return a {@link Mono} falling back upon source onError + * @see Flux#onErrorResume + */ + public final Mono onErrorResume(Class type, + Function> fallback) { + Objects.requireNonNull(type, "type"); + @SuppressWarnings("unchecked") + Function> handler = (Function>)fallback; + return onErrorResume(type::isInstance, handler); + } + + /** + * Subscribe to a fallback publisher when an error matching a given predicate + * occurs. + *

+ * + * + * @param predicate the error predicate to match + * @param fallback the function to choose the fallback to an alternative {@link Mono} + * @return a {@link Mono} falling back upon source onError + * @see Flux#onErrorResume + */ + public final Mono onErrorResume(Predicate predicate, + Function> fallback) { + Objects.requireNonNull(predicate, "predicate"); + return onErrorResume(e -> predicate.test(e) ? fallback.apply(e) : error(e)); + } + + /** + * Simply emit a captured fallback value when any error is observed on this {@link Mono}. + * + *

+ * + * + * @param fallback the value to emit if an error occurs + * + * @return a new falling back {@link Mono} + */ + public final Mono onErrorReturn(final T fallback) { + return onErrorResume(throwable -> just(fallback)); + } + + /** + * Simply emit a captured fallback value when an error of the specified type is + * observed on this {@link Mono}. + *

+ * + * + * @param type the error type to match + * @param fallbackValue the value to emit if an error occurs that matches the type + * @param the error type + * + * @return a new falling back {@link Mono} + */ + public final Mono onErrorReturn(Class type, T fallbackValue) { + return onErrorResume(type, throwable -> just(fallbackValue)); + } + + /** + * Simply emit a captured fallback value when an error matching the given predicate is + * observed on this {@link Mono}. + *

+ * + * + * @param predicate the error predicate to match + * @param fallbackValue the value to emit if an error occurs that matches the predicate + * + * @return a new {@link Mono} + */ + public final Mono onErrorReturn(Predicate predicate, T fallbackValue) { + return onErrorResume(predicate, throwable -> just(fallbackValue)); + } + + /** + * Detaches both the child {@link Subscriber} and the {@link Subscription} on + * termination or cancellation. + *

This should help with odd retention scenarios when running + * with non-reactor {@link Subscriber}. + * + * @return a detachable {@link Mono} + */ + public final Mono onTerminateDetach() { + return new MonoDetach<>(this); + } + + /** + * Share a {@link Mono} for the duration of a function that may transform it and + * consume it as many times as necessary without causing multiple subscriptions + * to the upstream. + * + * @param transform the transformation function + * @param the output value type + * + * @return a new {@link Mono} + */ + public final Mono publish(Function, ? extends Mono> transform) { + return onAssembly(new MonoPublishMulticast<>(this, transform)); + } + + /** + * Run onNext, onComplete and onError on a supplied {@link Scheduler} + * {@link Worker Worker}. + *

+ * This operator influences the threading context where the rest of the operators in + * the chain below it will execute, up to a new occurrence of {@code publishOn}. + *

+ * + *

+ * Typically used for fast publisher, slow consumer(s) scenarios. + * + *

+	 * {@code mono.publishOn(Schedulers.single()).subscribe() }
+	 * 
+ * + * @param scheduler a {@link Scheduler} providing the {@link Worker} where to publish + * + * @return an asynchronously producing {@link Mono} + */ + public final Mono publishOn(Scheduler scheduler) { + if(this instanceof Callable) { + if (this instanceof Fuseable.ScalarCallable) { + try { + T value = block(); + return onAssembly(new MonoSubscribeOnValue<>(value, scheduler)); + } + catch (Throwable t) { + //leave MonoSubscribeOnCallable defer error + } + } + @SuppressWarnings("unchecked") + Callable c = (Callable)this; + return onAssembly(new MonoSubscribeOnCallable<>(c, scheduler)); + } + return onAssembly(new MonoPublishOn<>(this, scheduler)); + } + + /** + * Repeatedly and indefinitely subscribe to the source upon completion of the + * previous subscription. + * + *

+ * + * + * @return an indefinitely repeated {@link Flux} on onComplete + */ + public final Flux repeat() { + return repeat(Flux.ALWAYS_BOOLEAN_SUPPLIER); + } + + /** + * Repeatedly subscribe to the source if the predicate returns true after completion of the previous subscription. + * + *

+ * + * + * @param predicate the boolean to evaluate on onComplete. + * + * @return a {@link Flux} that repeats on onComplete while the predicate matches + * + */ + public final Flux repeat(BooleanSupplier predicate) { + return Flux.onAssembly(new MonoRepeatPredicate<>(this, predicate)); + } + + /** + * Repeatedly subscribe to the source {@literal numRepeat} times. This results in + * {@code numRepeat + 1} total subscriptions to the original source. As a consequence, + * using 0 plays the original sequence once. + * + *

+ * + * + * @param numRepeat the number of times to re-subscribe on onComplete (positive, or 0 for original sequence only) + * @return a {@link Flux} that repeats on onComplete, up to the specified number of repetitions + */ + public final Flux repeat(long numRepeat) { + if (numRepeat == 0) { + return this.flux(); + } + return Flux.onAssembly(new MonoRepeat<>(this, numRepeat)); + } + + /** + * Repeatedly subscribe to the source if the predicate returns true after completion of the previous + * subscription. A specified maximum of repeat will limit the number of re-subscribe. + * + *

+ * + * + * @param numRepeat the number of times to re-subscribe on complete (positive, or 0 for original sequence only) + * @param predicate the boolean to evaluate on onComplete + * + * @return a {@link Flux} that repeats on onComplete while the predicate matches, + * up to the specified number of repetitions + */ + public final Flux repeat(long numRepeat, BooleanSupplier predicate) { + if (numRepeat < 0L) { + throw new IllegalArgumentException("numRepeat >= 0 required"); + } + if (numRepeat == 0) { + return this.flux(); + } + return Flux.defer(() -> repeat(Flux.countingBooleanSupplier(predicate, numRepeat))); + } + + /** + * Repeatedly subscribe to this {@link Mono} when a companion sequence emits elements in + * response to the flux completion signal. Any terminal signal from the companion + * sequence will terminate the resulting {@link Flux} with the same signal immediately. + *

If the companion sequence signals when this {@link Mono} is active, the repeat + * attempt is suppressed. + * + *

+ * + *

+ * Note that if the companion {@link Publisher} created by the {@code repeatFactory} + * emits {@link Context} as trigger objects, the content of these Context will be added + * to the operator's own {@link Context}. + * + * @param repeatFactory the {@link Function} that returns the associated {@link Publisher} + * companion, given a {@link Flux} that signals each onComplete as a {@link Long} + * representing the number of source elements emitted in the latest attempt (0 or 1). + * + * @return a {@link Flux} that repeats on onComplete when the companion {@link Publisher} produces an + * onNext signal + */ + public final Flux repeatWhen(Function, ? extends Publisher> repeatFactory) { + return Flux.onAssembly(new MonoRepeatWhen<>(this, repeatFactory)); + } + + /** + * Repeatedly subscribe to this {@link Mono} as long as the current subscription to this + * {@link Mono} completes empty and the companion {@link Publisher} produces an onNext signal. + *

+ * Any terminal signal will terminate the resulting {@link Mono} with the same signal immediately. + * + *

+ * + * + * @param repeatFactory the {@link Function} that returns the associated {@link Publisher} + * companion, given a {@link Flux} that signals each onComplete as a 0-based incrementing {@link Long}. + * + * @return a {@link Mono} that resubscribes to this {@link Mono} if the previous subscription was empty, + * as long as the companion {@link Publisher} produces an onNext signal + * + */ + public final Mono repeatWhenEmpty(Function, ? extends Publisher> repeatFactory) { + return repeatWhenEmpty(Integer.MAX_VALUE, repeatFactory); + } + + /** + * Repeatedly subscribe to this {@link Mono} as long as the current subscription to this + * {@link Mono} completes empty and the companion {@link Publisher} produces an onNext signal. + *

+ * Any terminal signal will terminate the resulting {@link Mono} with the same signal immediately. + *

+ * Emits an {@link IllegalStateException} if {@code maxRepeat} is exceeded (provided + * it is different from {@code Integer.MAX_VALUE}). + * + *

+ * + * + * @param maxRepeat the maximum number of repeats (infinite if {@code Integer.MAX_VALUE}) + * @param repeatFactory the {@link Function} that returns the associated {@link Publisher} + * companion, given a {@link Flux} that signals each onComplete as a 0-based incrementing {@link Long}. + * + * @return a {@link Mono} that resubscribes to this {@link Mono} if the previous subscription was empty, + * as long as the companion {@link Publisher} produces an onNext signal and the maximum number of repeats isn't exceeded. + */ + public final Mono repeatWhenEmpty(int maxRepeat, Function, ? extends Publisher> repeatFactory) { + return Mono.defer(() -> this.repeatWhen(o -> { + if (maxRepeat == Integer.MAX_VALUE) { + 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")))); + } + }).next()); + } + + + /** + * Re-subscribes to this {@link Mono} sequence if it signals any error, indefinitely. + *

+ * + * + * @return a {@link Mono} that retries on onError + */ + public final Mono retry() { + return retry(Long.MAX_VALUE); + } + + /** + * Re-subscribes to this {@link Mono} sequence if it signals any error, for a fixed + * number of times. + *

+ * Note that passing {@literal Long.MAX_VALUE} is treated as infinite retry. + *

+ * + * + * @param numRetries the number of times to tolerate an error + * + * @return a {@link Mono} that retries on onError up to the specified number of retry attempts. + */ + public final Mono retry(long numRetries) { + return onAssembly(new MonoRetry<>(this, numRetries)); + } + + /** + * Retries this {@link Mono} in response to signals emitted by a companion {@link Publisher}. + * 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} + * 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). + *

+ * Terminal signals in the companion terminate the sequence with the same signal, so emitting an {@link Subscriber#onError(Throwable)} + * will fail the resulting {@link Mono} with that same error. + *

+ * + *

+ * Note that the {@link reactor.util.retry.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. + *

+ * 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 + * the previous Context: + *

+ *
+	 * {@code
+	 * Retry customStrategy = Retry.from(companion -> companion.handle((retrySignal, sink) -> {
+	 * 	    Context ctx = sink.currentContext();
+	 * 	    int rl = ctx.getOrDefault("retriesLeft", 0);
+	 * 	    if (rl > 0) {
+	 *		    sink.next(Context.of(
+	 *		        "retriesLeft", rl - 1,
+	 *		        "lastError", retrySignal.failure()
+	 *		    ));
+	 * 	    } else {
+	 * 	        sink.error(Exceptions.retryExhausted("retries exhausted", retrySignal.failure()));
+	 * 	    }
+	 * }));
+	 * Mono retried = originalMono.retryWhen(customStrategy);
+	 * }
+ *
+ * + * @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}. + * + * @return a {@link Mono} that retries on onError when a companion {@link Publisher} produces an onNext signal + * @see Retry#max(long) + * @see Retry#maxInARow(long) + * @see Retry#backoff(long, Duration) + */ + public final Mono retryWhen(Retry retrySpec) { + return onAssembly(new MonoRetryWhen<>(this, retrySpec)); + } + + /** + * Prepare a {@link Mono} which shares this {@link Mono} result similar to {@link Flux#shareNext()}. + * This will effectively turn this {@link Mono} into a hot task when the first + * {@link Subscriber} subscribes using {@link #subscribe()} API. Further {@link Subscriber} will share the same {@link Subscription} + * and therefore the same result. + * It's worth noting this is an un-cancellable {@link Subscription}. + *

+ * + * + * @return a new {@link Mono} + */ + public final Mono share() { + if (this instanceof Fuseable.ScalarCallable) { + return this; + } + + if (this instanceof NextProcessor && ((NextProcessor) this).isRefCounted) { //TODO should we check whether the NextProcessor has a source or not? + return this; + } + return new NextProcessor<>(this, true); + } + + /** + * Expect exactly one item from this {@link Mono} source or signal + * {@link java.util.NoSuchElementException} for an empty source. + *

+ * + *

+ * Note Mono doesn't need {@link Flux#single(Object)}, since it is equivalent to + * {@link #defaultIfEmpty(Object)} in a {@link Mono}. + * + * @return a {@link Mono} with the single item or an error signal + */ + public final Mono single() { + 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)); + } + if (v == null) { + return Mono.error(new NoSuchElementException("Source was a (constant) empty")); + } + return Mono.just(v); + } + @SuppressWarnings("unchecked") + Callable thiz = (Callable)this; + return Mono.onAssembly(new MonoSingleCallable<>(thiz)); + } + return Mono.onAssembly(new MonoSingleMono<>(this)); + } + + /** + * Subscribe to this {@link Mono} and request unbounded demand. + *

+ * This version doesn't specify any consumption behavior for the events from the + * chain, especially no error handling, so other variants should usually be preferred. + * + *

+ * + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe() { + if(this instanceof NextProcessor){ + NextProcessor s = (NextProcessor)this; + // Mono#share() should return a new lambda subscriber during subscribe. + // This can now be precisely detected with source != null in combination to the isRefCounted boolean being true. + // `Sinks.one().subscribe()` case is now split into a separate implementation. + // Otherwise, this is a (legacy) #toProcessor() usage, and we return the processor itself below (and don't forget to connect() it): + if (s.source != null && !s.isRefCounted) { + s.subscribe(new LambdaMonoSubscriber<>(null, null, null, null, null)); + s.connect(); + return s; + } + } + return subscribeWith(new LambdaMonoSubscriber<>(null, null, null, null, null)); + } + + /** + * 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)}. + *

+ * 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 + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each value (onNext signal) + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe(Consumer consumer) { + Objects.requireNonNull(consumer, "consumer"); + return subscribe(consumer, null, null); + } + + /** + * Subscribe to this {@link Mono} with a {@link Consumer} that will consume all the + * elements in the sequence, as well as a {@link Consumer} that will handle errors. + * 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)}. + *

+ * 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 + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each next signal + * @param errorConsumer the consumer to invoke on error signal + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe(@Nullable Consumer consumer, Consumer errorConsumer) { + Objects.requireNonNull(errorConsumer, "errorConsumer"); + return subscribe(consumer, errorConsumer, null); + } + + /** + * Subscribe {@link Consumer} to this {@link Mono} that will respectively consume all the + * 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 #doOnSuccess(Consumer)} and + * {@link #doOnError(java.util.function.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 + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each value + * @param errorConsumer the consumer to invoke on error signal + * @param completeConsumer the consumer to invoke on complete signal + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe( + @Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer) { + return subscribe(consumer, errorConsumer, completeConsumer, (Context) null); + } + + /** + * Subscribe {@link Consumer} to this {@link Mono} that will respectively consume all the + * elements in the sequence, handle errors, react to completion, and request upon subscription. + * It will let the provided {@link Subscription subscriptionConsumer} + * 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 #doOnSuccess(Consumer)} and + * {@link #doOnError(java.util.function.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 + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each value + * @param errorConsumer the consumer to invoke on error signal + * @param completeConsumer the consumer to invoke on complete signal + * @param subscriptionConsumer the consumer to invoke on subscribe signal, to be used + * for the initial {@link Subscription#request(long) request}, or null for max request + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ //TODO maybe deprecate in 3.4, provided there is at least an alternative for tests + public final Disposable subscribe( + @Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer, + @Nullable Consumer subscriptionConsumer) { + return subscribeWith(new LambdaMonoSubscriber<>(consumer, errorConsumer, + completeConsumer, subscriptionConsumer, null)); + } + + /** + * Subscribe {@link Consumer} to this {@link Mono} that will respectively consume all the + * 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 #doOnSuccess(Consumer)} and + * {@link #doOnError(java.util.function.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 + * not invoked when executing in a main thread or a unit test for instance. + * + *

+ * + * + * @param consumer the consumer to invoke on each value + * @param errorConsumer the consumer to invoke on error signal + * @param completeConsumer the consumer to invoke on complete signal + * @param initialContext the {@link Context} for the subscription + * + * @return a new {@link Disposable} that can be used to cancel the underlying {@link Subscription} + */ + public final Disposable subscribe( + @Nullable Consumer consumer, + @Nullable Consumer errorConsumer, + @Nullable Runnable completeConsumer, + @Nullable Context initialContext) { + return subscribeWith(new LambdaMonoSubscriber<>(consumer, errorConsumer, + completeConsumer, null, initialContext)); + } + + @Override + @SuppressWarnings("unchecked") + public final void subscribe(Subscriber actual) { + CorePublisher publisher = Operators.onLastAssembly(this); + CoreSubscriber subscriber = Operators.toCoreSubscriber(actual); + + try { + if (publisher instanceof OptimizableOperator) { + OptimizableOperator operator = (OptimizableOperator) publisher; + while (true) { + subscriber = operator.subscribeOrReturn(subscriber); + if (subscriber == null) { + // null means "I will subscribe myself", returning... + return; + } + + OptimizableOperator newSource = operator.nextOptimizableSource(); + if (newSource == null) { + publisher = operator.source(); + break; + } + operator = newSource; + } + } + + publisher.subscribe(subscriber); + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(subscriber, e); + return; + } + } + + /** + * An internal {@link Publisher#subscribe(Subscriber)} that will bypass + * {@link Hooks#onLastOperator(Function)} pointcut. + *

+ * In addition to behave as expected by {@link Publisher#subscribe(Subscriber)} + * in a controlled manner, it supports direct subscribe-time {@link Context} passing. + * + * @param actual the {@link Subscriber} interested into the published sequence + * @see Publisher#subscribe(Subscriber) + */ + 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 + * the next occurrence of a {@link #publishOn(Scheduler) publishOn}. + *

+ * + * + *

+	 * {@code mono.subscribeOn(Schedulers.parallel()).subscribe()) }
+	 * 
+ * + * @param scheduler a {@link Scheduler} providing the {@link Worker} where to subscribe + * + * @return a {@link Mono} requesting asynchronously + * @see #publishOn(Scheduler) + */ + public final Mono subscribeOn(Scheduler scheduler) { + if(this instanceof Callable) { + if (this instanceof Fuseable.ScalarCallable) { + try { + T value = block(); + return onAssembly(new MonoSubscribeOnValue<>(value, scheduler)); + } + catch (Throwable t) { + //leave MonoSubscribeOnCallable defer error + } + } + @SuppressWarnings("unchecked") + Callable c = (Callable)this; + return onAssembly(new MonoSubscribeOnCallable<>(c, + scheduler)); + } + return onAssembly(new MonoSubscribeOn<>(this, scheduler)); + } + + /** + * Subscribe the given {@link Subscriber} to this {@link Mono} and return said + * {@link Subscriber} (eg. a {@link MonoProcessor}). + * + * @param subscriber the {@link Subscriber} to subscribe with + * @param the reified type of the {@link Subscriber} for chaining + * + * @return the passed {@link Subscriber} after subscribing it to this {@link Mono} + */ + public final > E subscribeWith(E subscriber) { + subscribe(subscriber); + return subscriber; + } + + /** + * Fallback to an alternative {@link Mono} if this mono is completed without data + * + *

+ * + * + * @param alternate the alternate mono if this mono is empty + * + * @return a {@link Mono} falling back upon source completing without elements + * @see Flux#switchIfEmpty + */ + public final Mono switchIfEmpty(Mono alternate) { + return onAssembly(new MonoSwitchIfEmpty<>(this, alternate)); + } + + /** + * 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()}). + *

+ * Note that some monitoring systems like Prometheus require to have the exact same set of + * tags for each meter bearing the same name. + * + * @param key a tag key + * @param value a tag value + * + * @return the same sequence, but bearing tags + * + * @see #name(String) + * @see #metrics() + */ + public final Mono tag(String key, String value) { + return MonoName.createOrAppend(this, key, value); + } + + /** + * Give this Mono a chance to resolve within a specified time frame but complete if it + * doesn't. This works a bit like {@link #timeout(Duration)} except that the resulting + * {@link Mono} completes rather than errors when the timer expires. + *

+ * + *

+ * The timeframe is evaluated using the {@link Schedulers#parallel() parallel Scheduler}. + * + * @param duration the maximum duration to wait for the source Mono to resolve. + * @return a new {@link Mono} that will propagate the signals from the source unless + * no signal is received for {@code duration}, in which case it completes. + */ + public final Mono take(Duration duration) { + return take(duration, Schedulers.parallel()); + } + + /** + * Give this Mono a chance to resolve within a specified time frame but complete if it + * doesn't. This works a bit like {@link #timeout(Duration)} except that the resulting + * {@link Mono} completes rather than errors when the timer expires. + *

+ * + *

+ * The timeframe is evaluated using the provided {@link Scheduler}. + * + * @param duration the maximum duration to wait for the source Mono to resolve. + * @param timer the {@link Scheduler} on which to measure the duration. + * + * @return a new {@link Mono} that will propagate the signals from the source unless + * no signal is received for {@code duration}, in which case it completes. + */ + public final Mono take(Duration duration, Scheduler timer) { + return takeUntilOther(Mono.delay(duration, timer)); + } + + /** + * Give this Mono a chance to resolve before a companion {@link Publisher} emits. If + * the companion emits before any signal from the source, the resulting Mono will + * complete. Otherwise, it will relay signals from the source. + *

+ * + * + * @param other a companion {@link Publisher} that shortcircuits the source with an + * onComplete signal if it emits before the source emits. + * + * @return a new {@link Mono} that will propagate the signals from the source unless + * a signal is first received from the companion {@link Publisher}, in which case it + * completes. + */ + public final Mono takeUntilOther(Publisher other) { + return onAssembly(new MonoTakeUntilOther<>(this, other)); + } + + /** + * Return a {@code Mono} which only replays complete and error signals + * from this {@link Mono}. + * + *

+ * + * + *

Discard Support: This operator discards the element from the source. + * + * @return a {@link Mono} ignoring its payload (actively dropping) + */ + public final Mono then() { + return empty(this); + } + + /** + * Let this {@link Mono} complete then play another Mono. + *

+ * In other words ignore element from this {@link Mono} and transform its completion signal into the + * emission and completion signal of a provided {@code Mono}. Error signal is + * replayed in the resulting {@code Mono}. + * + *

+ * + * + *

Discard Support: This operator discards the element from the source. + * + * @param other a {@link Mono} to emit from after termination + * @param the element type of the supplied Mono + * + * @return a new {@link Mono} that emits from the supplied {@link Mono} + */ + public final Mono then(Mono other) { + if (this instanceof MonoIgnoreThen) { + MonoIgnoreThen a = (MonoIgnoreThen) this; + return a.shift(other); + } + return onAssembly(new MonoIgnoreThen<>(new Publisher[] { this }, other)); + } + + /** + * Let this {@link Mono} complete successfully, then emit the provided value. On an error in the original {@link Mono}, the error signal is propagated instead. + *

+ * + * + *

Discard Support: This operator discards the element from the source. + * + * @param value a value to emit after successful termination + * @param the element type of the supplied value + * + * @return a new {@link Mono} that emits the supplied value + */ + public final Mono thenReturn(V value) { + return then(Mono.just(value)); + } + + /** + * Return a {@code Mono} that waits for this {@link Mono} to complete then + * for a supplied {@link Publisher Publisher<Void>} to also complete. The + * second completion signal is replayed, or any error signal that occurs instead. + *

+ * + * + *

Discard Support: This operator discards the element from the source. + * + * @param other a {@link Publisher} to wait for after this Mono's termination + * @return a new {@link Mono} completing when both publishers have completed in + * sequence + */ + public final Mono thenEmpty(Publisher other) { + return then(fromDirect(other)); + } + + /** + * Let this {@link Mono} complete successfully then play another {@link Publisher}. On an error in the original {@link Mono}, the error signal is propagated instead. + *

+ * In other words ignore the element from this mono and transform the completion signal into a + * {@code Flux} that will emit elements from the provided {@link Publisher}. + * + *

+ * + * + *

Discard Support: This operator discards the element from the source. + * + * @param other a {@link Publisher} to emit from after termination + * @param the element type of the supplied Publisher + * + * @return a new {@link Flux} that emits from the supplied {@link Publisher} after + * this Mono completes. + */ + public final Flux thenMany(Publisher other) { + @SuppressWarnings("unchecked") + Flux concat = (Flux)Flux.concat(ignoreElement(), other); + return Flux.onAssembly(concat); + } + + + /** + * Times this {@link Mono} {@link Subscriber#onNext(Object)} event, encapsulated into a {@link Timed} object + * that lets downstream consumer look at various time information gathered with nanosecond + * resolution using the default clock ({@link Schedulers#parallel()}): + *

    + *
  • {@link Timed#elapsed()}: the time in nanoseconds since subscription, as a {@link Duration}. + * This is functionally equivalent to {@link #elapsed()}, with a more expressive and precise + * representation than a {@link Tuple2} with a long.
  • + *
  • {@link Timed#timestamp()}: the timestamp of this onNext, as an {@link java.time.Instant} + * (with nanoseconds part). This is functionally equivalent to {@link #timestamp()}, with a more + * expressive and precise representation than a {@link Tuple2} with a long.
  • + *
  • {@link Timed#elapsedSinceSubscription()}: for {@link Mono} this is the same as + * {@link Timed#elapsed()}.
  • + *
+ *

+ * The {@link Timed} object instances are safe to store and use later, as they are created as an + * immutable wrapper around the {@code } value and immediately passed downstream. + *

+ * + * + * @return a timed {@link Mono} + * @see #elapsed() + * @see #timestamp() + */ + public final Mono> timed() { + return this.timed(Schedulers.parallel()); + } + + /** + * Times this {@link Mono} {@link Subscriber#onNext(Object)} event, encapsulated into a {@link Timed} object + * that lets downstream consumer look at various time information gathered with nanosecond + * resolution using the provided {@link Scheduler} as a clock: + *

    + *
  • {@link Timed#elapsed()}: the time in nanoseconds since subscription, as a {@link Duration}. + * This is functionally equivalent to {@link #elapsed()}, with a more expressive and precise + * representation than a {@link Tuple2} with a long.
  • + *
  • {@link Timed#timestamp()}: the timestamp of this onNext, as an {@link java.time.Instant} + * (with nanoseconds part). This is functionally equivalent to {@link #timestamp()}, with a more + * expressive and precise representation than a {@link Tuple2} with a long.
  • + *
  • {@link Timed#elapsedSinceSubscription()}: for {@link Mono} this is the same as + * {@link Timed#elapsed()}.
  • + *
+ *

+ * The {@link Timed} object instances are safe to store and use later, as they are created as an + * immutable wrapper around the {@code } value and immediately passed downstream. + *

+ * + * + * @return a timed {@link Mono} + * @see #elapsed(Scheduler) + * @see #timestamp(Scheduler) + */ + public final Mono> timed(Scheduler clock) { + return onAssembly(new MonoTimed<>(this, clock)); + } + + /** + * Propagate a {@link TimeoutException} in case no item arrives within the given + * {@link Duration}. + * + *

+ * + * + * @param timeout the timeout before the onNext signal from this {@link Mono} + * + * @return a {@link Mono} that can time out + */ + public final Mono timeout(Duration timeout) { + return timeout(timeout, Schedulers.parallel()); + } + + /** + * Switch to a fallback {@link Mono} in case no item arrives within the given {@link Duration}. + * + *

+ * If the fallback {@link Mono} is null, signal a {@link TimeoutException} instead. + * + *

+ * + * + * @param timeout the timeout before the onNext signal from this {@link Mono} + * @param fallback the fallback {@link Mono} to subscribe to when a timeout occurs + * + * @return a {@link Mono} that will fallback to a different {@link Mono} in case of timeout + */ + public final Mono timeout(Duration timeout, Mono fallback) { + return timeout(timeout, fallback, Schedulers.parallel()); + } + + /** + * Signal a {@link TimeoutException} error in case an item doesn't arrive before the given period, + * as measured on the provided {@link Scheduler}. + * + *

+ * + * + * @param timeout the timeout before the onNext signal from this {@link Mono} + * @param timer a time-capable {@link Scheduler} instance to run the delay on + * + * @return an expirable {@link Mono} + */ + public final Mono timeout(Duration timeout, Scheduler timer) { + return timeout(timeout, null, timer); + } + + /** + * Switch to a fallback {@link Mono} in case an item doesn't arrive before the given period, + * as measured on the provided {@link Scheduler}. + * + *

If the given {@link Mono} is null, signal a {@link TimeoutException}. + * + *

+ * + * + * @param timeout the timeout before the onNext signal from this {@link Mono} + * @param fallback the fallback {@link Mono} to subscribe when a timeout occurs + * @param timer a time-capable {@link Scheduler} instance to run on + * + * @return an expirable {@link Mono} with a fallback {@link Mono} + */ + public final Mono timeout(Duration timeout, @Nullable Mono fallback, + Scheduler timer) { + final Mono _timer = Mono.delay(timeout, timer).onErrorReturn(0L); + + if(fallback == null) { + return onAssembly(new MonoTimeout<>(this, _timer, timeout.toMillis() + "ms")); + } + return onAssembly(new MonoTimeout<>(this, _timer, fallback)); + } + + /** + * Signal a {@link TimeoutException} in case the item from this {@link Mono} has + * not been emitted before the given {@link Publisher} emits. + * + *

+ * + * + * @param firstTimeout the timeout {@link Publisher} that must not emit before the first signal from this {@link Mono} + * @param the element type of the timeout Publisher + * + * @return an expirable {@link Mono} if the item does not come before a {@link Publisher} signals + * + */ + public final Mono timeout(Publisher firstTimeout) { + return onAssembly(new MonoTimeout<>(this, firstTimeout, "first signal from a Publisher")); + } + + /** + * Switch to a fallback {@link Publisher} in case the item from this {@link Mono} has + * not been emitted before the given {@link Publisher} emits. + * + *

+ * + * + * @param firstTimeout the timeout + * {@link Publisher} that must not emit before the first signal from this {@link Mono} + * @param fallback the fallback {@link Publisher} to subscribe when a timeout occurs + * @param the element type of the timeout Publisher + * + * @return an expirable {@link Mono} with a fallback {@link Mono} if the item doesn't + * come before a {@link Publisher} signals + * + */ + public final Mono timeout(Publisher firstTimeout, Mono fallback) { + return onAssembly(new MonoTimeout<>(this, firstTimeout, fallback)); + } + + /** + * If this {@link Mono} is valued, emit a {@link reactor.util.function.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}). + * + *

+ * + * + * @return a timestamped {@link Mono} + * @see #timed() + */ + public final Mono> timestamp() { + return timestamp(Schedulers.parallel()); + } + + /** + * If this {@link Mono} is valued, emit a {@link reactor.util.function.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}). + * + *

The provider {@link Scheduler} will be asked to {@link Scheduler#now(TimeUnit) provide time} + * with a granularity of {@link TimeUnit#MILLISECONDS}. In order for this operator to work as advertised, the + * provided Scheduler should thus return results that can be interpreted as unix timestamps.

+ *

+ * + * + * + * @param scheduler a {@link Scheduler} instance to read time from + * @return a timestamped {@link Mono} + * @see Scheduler#now(TimeUnit) + * @see #timed(Scheduler) + */ + public final Mono> timestamp(Scheduler scheduler) { + Objects.requireNonNull(scheduler, "scheduler"); + return map(d -> Tuples.of(scheduler.now(TimeUnit.MILLISECONDS), d)); + } + + /** + * Transform this {@link Mono} into a {@link CompletableFuture} completing on onNext or onComplete and failing on + * onError. + * + *

+ * + * + * @return a {@link CompletableFuture} + */ + public final CompletableFuture toFuture() { + return subscribeWith(new MonoToCompletableFuture<>(false)); + } + + /** + * 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. + * + *

+	 * Function applySchedulers = mono -> mono.subscribeOn(Schedulers.io())
+	 *                                                    .publishOn(Schedulers.parallel());
+	 * mono.transform(applySchedulers).map(v -> v * v).subscribe();
+	 * 
+ *

+ * + * + * @param transformer the {@link Function} to immediately map this {@link Mono} into a target {@link Mono} + * instance. + * @param the item type in the returned {@link Mono} + * + * @return a new {@link Mono} + * @see #transformDeferred(Function) transformDeferred(Function) for deferred composition of Mono for each Subscriber + * @see #as(Function) as(Function) for a loose conversion to an arbitrary type + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public final Mono transform(Function, ? extends Publisher> transformer) { + if (Hooks.DETECT_CONTEXT_LOSS) { + transformer = new ContextTrackingFunctionWrapper(transformer); + } + return onAssembly(from(transformer.apply(this))); + } + + /** + * Defer the given transformation to this {@link Mono} in order to generate a + * target {@link Mono} type. A transformation will occur for each + * {@link Subscriber}. For instance: + * + *

+	 * mono.transformDeferred(original -> original.log());
+	 * 
+ *

+ * + * + * @param transformer the {@link Function} to lazily map this {@link Mono} into a target {@link Mono} + * instance upon subscription. + * @param the item type in the returned {@link Publisher} + * + * @return a new {@link Mono} + * @see #transform(Function) transform(Function) for immmediate transformation of Mono + * @see #transformDeferredContextual(BiFunction) transformDeferredContextual(BiFunction) for a similarly deferred transformation of Mono reading the ContextView + * @see #as(Function) as(Function) for a loose conversion to an arbitrary type + */ + public final Mono transformDeferred(Function, ? extends Publisher> transformer) { + return defer(() -> { + if (Hooks.DETECT_CONTEXT_LOSS) { + @SuppressWarnings({"unchecked", "rawtypes"}) + Mono result = from(new ContextTrackingFunctionWrapper((Function) transformer).apply(this)); + return result; + } + return from(transformer.apply(this)); + }); + } + + /** + * Defer the given transformation to this {@link Mono} in order to generate a + * target {@link Mono} type. A transformation will occur for each + * {@link Subscriber}. In addition, the transforming {@link BiFunction} exposes + * the {@link ContextView} of each {@link Subscriber}. For instance: + * + *

+	 * Mono<T> monoLogged = mono.transformDeferredContextual((original, ctx) -> original.log("for RequestID" + ctx.get("RequestID"))
+	 * //...later subscribe. Each subscriber has its Context with a RequestID entry
+	 * monoLogged.contextWrite(Context.of("RequestID", "requestA").subscribe();
+	 * monoLogged.contextWrite(Context.of("RequestID", "requestB").subscribe();
+	 * 
+ *

+ * + * + * @param transformer the {@link BiFunction} to lazily map this {@link Mono} into a target {@link Mono} + * instance upon subscription, with access to {@link ContextView} + * @param the item type in the returned {@link Publisher} + * @return a new {@link Mono} + * @see #transform(Function) transform(Function) for immmediate transformation of Mono + * @see #transformDeferred(Function) transformDeferred(Function) for a similarly deferred transformation of Mono without the ContextView + * @see #as(Function) as(Function) for a loose conversion to an arbitrary type + */ + public final Mono transformDeferredContextual(BiFunction, ? super ContextView, ? extends Publisher> transformer) { + return deferContextual(ctxView -> { + if (Hooks.DETECT_CONTEXT_LOSS) { + ContextTrackingFunctionWrapper wrapper = new ContextTrackingFunctionWrapper<>( + publisher -> transformer.apply(wrap(publisher, false), ctxView), + transformer.toString() + ); + return wrap(wrapper.apply(this), true); + } + return from(transformer.apply(this, ctxView)); + }); + } + + /** + * Wait for the result from this mono, use it to create a second mono via the + * provided {@code rightGenerator} function and combine both results into a {@link Tuple2}. + * + *

+ * + * + * @param rightGenerator the {@link Function} to generate a {@code Mono} to combine with + * @param the element type of the other Mono instance + * + * @return a new combined Mono + */ + public final Mono> zipWhen(Function> rightGenerator) { + return zipWhen(rightGenerator, Tuples::of); + } + + /** + * Wait for the result from this mono, use it to create a second mono via the + * provided {@code rightGenerator} function and combine both results into an arbitrary + * {@code O} object, as defined by the provided {@code combinator} function. + * + *

+ * + * + * @param rightGenerator the {@link Function} to generate a {@code Mono} to combine with + * @param combinator a {@link BiFunction} combinator function when both sources complete + * @param the element type of the other Mono instance + * @param the element type of the combination + * + * @return a new combined Mono + */ + public final Mono zipWhen(Function> rightGenerator, + BiFunction combinator) { + Objects.requireNonNull(rightGenerator, "rightGenerator function is mandatory to get the right-hand side Mono"); + Objects.requireNonNull(combinator, "combinator function is mandatory to combine results from both Monos"); + return flatMap(t -> rightGenerator.apply(t).map(t2 -> combinator.apply(t, t2))); + } + + /** + * Combine the result from this mono and another into a {@link Tuple2}. + *

+ * An error or empty completion of any source will cause the other source + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + * + * @param other the {@link Mono} to combine with + * @param the element type of the other Mono instance + * + * @return a new combined Mono + */ + public final Mono> zipWith(Mono other) { + return zipWith(other, Flux.tuple2Function()); + } + + /** + * Combine the result from this mono and another into an arbitrary {@code O} object, + * as defined by the provided {@code combinator} function. + *

+ * An error or empty completion of any source will cause the other source + * to be cancelled and the resulting Mono to immediately error or complete, respectively. + * + *

+ * + * + * @param other the {@link Mono} to combine with + * @param combinator a {@link BiFunction} combinator function when both sources + * complete + * @param the element type of the other Mono instance + * @param the element type of the combination + * + * @return a new combined Mono + */ + public final Mono zipWith(Mono other, + BiFunction combinator) { + if (this instanceof MonoZip) { + @SuppressWarnings("unchecked") MonoZip o = (MonoZip) this; + Mono result = o.zipAdditionalSource(other, combinator); + if (result != null) { + return result; + } + } + + return zip(this, other, combinator); + } + + /** + * To be used by custom operators: invokes assembly {@link Hooks} pointcut given a + * {@link Mono}, potentially returning a new {@link Mono}. This is for example useful + * to activate cross-cutting concerns at assembly time, eg. a generalized + * {@link #checkpoint()}. + * + * @param the value type + * @param source the source to apply assembly hooks onto + * + * @return the source, potentially wrapped with assembly time cross-cutting behavior + */ + @SuppressWarnings("unchecked") + protected static Mono onAssembly(Mono source) { + Function hook = Hooks.onEachOperatorHook; + if(hook != null) { + source = (Mono) hook.apply(source); + } + if (Hooks.GLOBAL_TRACE) { + AssemblySnapshot stacktrace = new AssemblySnapshot(null, Traces.callSiteSupplierFactory.get()); + source = (Mono) Hooks.addAssemblyInfo(source, stacktrace); + } + return source; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + + static Mono empty(Publisher source) { + @SuppressWarnings("unchecked") + Mono then = (Mono)ignoreElements(source); + return then; + } + + static Mono doOnSignal(Mono source, + @Nullable Consumer onSubscribe, + @Nullable Consumer onNext, + @Nullable LongConsumer onRequest, + @Nullable Runnable onCancel) { + if (source instanceof Fuseable) { + return onAssembly(new MonoPeekFuseable<>(source, + onSubscribe, + onNext, + onRequest, + onCancel)); + } + return onAssembly(new MonoPeek<>(source, + onSubscribe, + onNext, + onRequest, + onCancel)); + } + + static Mono doOnTerminalSignal(Mono source, + @Nullable Consumer onSuccess, + @Nullable Consumer onError, + @Nullable BiConsumer onAfterTerminate) { + return onAssembly(new MonoPeekTerminal<>(source, onSuccess, onError, onAfterTerminate)); + } + + /** + * Unchecked wrap of {@link Publisher} as {@link Mono}, supporting {@link Fuseable} sources. + * When converting a {@link Mono} or {@link Mono Monos} that have been converted to a {@link Flux} and back, + * the original {@link Mono} is returned unwrapped. + * Note that this bypasses {@link Hooks#onEachOperator(String, Function) assembly hooks}. + * + * @param source the {@link Publisher} to wrap + * @param enforceMonoContract {@code} true to wrap publishers without assumption about their cardinality + * (first {@link Subscriber#onNext(Object)} will cancel the source), {@code false} to behave like {@link #fromDirect(Publisher)}. + * @param input upstream type + * @return a wrapped {@link Mono} + */ + static Mono wrap(Publisher source, boolean enforceMonoContract) { + //some sources can be considered already assembled monos + //all conversion methods (from, fromDirect, wrap) must accommodate for this + if (source instanceof Mono) { + return (Mono) source; + } + if (source instanceof FluxSourceMono + || source instanceof FluxSourceMonoFuseable) { + @SuppressWarnings("unchecked") + Mono extracted = (Mono) ((FluxFromMonoOperator) source).source; + return extracted; + } + + //equivalent to what from used to be, without assembly hooks + if (enforceMonoContract) { + if (source instanceof Flux && source instanceof Callable) { + @SuppressWarnings("unchecked") Callable m = (Callable) source; + return Flux.wrapToMono(m); + } + if (source instanceof Flux) { + return new MonoNext<>((Flux) source); + } + return new MonoFromPublisher<>(source); + } + + //equivalent to what fromDirect used to be without onAssembly + if(source instanceof Flux && source instanceof Fuseable) { + return new MonoSourceFluxFuseable<>((Flux) source); + } + if (source instanceof Flux) { + return new MonoSourceFlux<>((Flux) source); + } + if(source instanceof Fuseable) { + return new MonoSourceFuseable<>(source); + } + return new MonoSource<>(source); + } + + @SuppressWarnings("unchecked") + static BiPredicate equalsBiPredicate(){ + return EQUALS_BIPREDICATE; + } + static final BiPredicate EQUALS_BIPREDICATE = Object::equals; +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoAll.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoAll.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoAll.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Emits a single boolean true if all values of the source sequence match + * the predicate. + *

+ * The implementation uses short-circuit logic and completes with false if + * the predicate doesn't match a value. + * + * @param the source value type + * @see Reactive-Streams-Commons + */ +final class MonoAll extends MonoFromFluxOperator + implements Fuseable { + + final Predicate predicate; + + MonoAll(Flux source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new AllSubscriber(actual, predicate); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class AllSubscriber extends Operators.MonoSubscriber { + final Predicate predicate; + + Subscription s; + + boolean done; + + AllSubscriber(CoreSubscriber actual, Predicate predicate) { + super(actual); + this.predicate = predicate; + } + + @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 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) { + return; + } + + boolean b; + + try { + b = predicate.test(t); + } catch (Throwable e) { + done = true; + actual.onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + if (!b) { + done = true; + s.cancel(); + + complete(false); + } + } + + @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(true); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoAny.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoAny.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoAny.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Emits a single boolean true if any of the values of the source sequence match + * the predicate. + *

+ * The implementation uses short-circuit logic and completes with true if + * the predicate matches a value. + * + * @param the source value type + * @see Reactive-Streams-Commons + */ +final class MonoAny extends MonoFromFluxOperator + implements Fuseable { + + final Predicate predicate; + + MonoAny(Flux source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new AnySubscriber(actual, predicate); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class AnySubscriber extends Operators.MonoSubscriber { + final Predicate predicate; + + Subscription s; + + boolean done; + + AnySubscriber(CoreSubscriber actual, Predicate predicate) { + super(actual); + this.predicate = predicate; + } + + @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 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) { + return; + } + + boolean b; + + try { + b = predicate.test(t); + } catch (Throwable e) { + done = true; + actual.onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return; + } + if (b) { + done = true; + s.cancel(); + + complete(true); + } + } + + @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(false); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoBridges.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoBridges.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoBridges.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016-2021 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.Function; + +import org.reactivestreams.Publisher; + +/** + * Utilities to avoid vararg array copying overhead when relaying vararg parameters + * to underlying Java methods from their corresponding Kotlin functions. + *

+ * When this issue is + * resolved, uses of these bridge methods can be removed. + * + * @author DoHyung Kim + * @since 3.1 + */ +final class MonoBridges { + + static Mono zip(Function combinator, Mono[] monos) { + return Mono.zip(combinator, monos); + } + + static Mono when(Publisher[] sources) { + return Mono.when(sources); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCacheInvalidateIf.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCacheInvalidateIf.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCacheInvalidateIf.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2021 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.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Predicate; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.util.Logger; +import reactor.util.Loggers; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A caching operator that uses a {@link Predicate} to potentially invalidate the cached value whenever a late subscriber arrives. + * Subscribers accumulated between the upstream susbscription is triggered and the actual value is received and stored + * don't trigger the predicate, as the operator assumes that values are not invalid at reception. + * + * @author Simon Baslé + */ +final class MonoCacheInvalidateIf extends InternalMonoOperator { + + /** + * A state-holding interface for {@link MonoCacheInvalidateIf} and {@link MonoCacheInvalidateWhen}, + * leaner than a {@link Signal} to represent valued state, and allowing specific subclasses for pending + * state and disconnected state. + * + * @param the type of data cached by the parent operator + */ + static interface State { + + @Nullable + T get(); + + void clear(); + } + + /** + * Common implementation of {@link State} for storing the cached value. + * + * @param the type of data cached by the parent operator + */ + static final class ValueState implements State { + + @Nullable + T value; + + ValueState(T value) { + this.value = value; + } + + @Nullable + @Override + public T get() { + return value; + } + + @Override + public void clear() { + this.value = null; + } + } + + /** + * Singleton implementation of an empty {@link State}, to represent initial state + * pre-subscription, when no caching has been requested. + */ + static final State EMPTY_STATE = new State() { + @Nullable + @Override + public Object get() { + return null; + } + + @Override + public void clear() { + //NO-OP + } + }; + + final Predicate shouldInvalidatePredicate; + + volatile State state; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater STATE = + AtomicReferenceFieldUpdater.newUpdater(MonoCacheInvalidateIf.class, State.class, "state"); + + MonoCacheInvalidateIf(Mono source, Predicate invalidationPredicate) { + super(source); + this.shouldInvalidatePredicate = Objects.requireNonNull(invalidationPredicate, "invalidationPredicate"); + @SuppressWarnings("unchecked") + State state = (State) EMPTY_STATE; + this.state = state; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CacheMonoSubscriber inner = new CacheMonoSubscriber<>(actual); + //important: onSubscribe should be deferred until we're sure we're in the Coordinator case OR the cached value passes the predicate + for(;;) { + State state = this.state; + if (state == EMPTY_STATE || state instanceof CoordinatorSubscriber) { + boolean connectToUpstream = false; + CoordinatorSubscriber coordinator; + if (state == EMPTY_STATE) { + coordinator = new CoordinatorSubscriber<>(this, this.source); + if (!STATE.compareAndSet(this, EMPTY_STATE, coordinator)) { + continue; + } + connectToUpstream = true; + } + else { + coordinator = (CoordinatorSubscriber) state; + } + + if (coordinator.add(inner)) { + if (inner.isCancelled()) { + coordinator.remove(inner); + } + else { + inner.coordinator = coordinator; + actual.onSubscribe(inner); + } + + if (connectToUpstream) { + coordinator.delayedSubscribe(); + } + return null; + } + } + else { + //state is an actual signal, cached + T cached = state.get(); + try { + boolean invalidated = this.shouldInvalidatePredicate.test(cached); + if (invalidated) { + if (STATE.compareAndSet(this, state, EMPTY_STATE)) { + Operators.onDiscard(cached, actual.currentContext()); + } + //we CAS but even if it fails we want to loop back + continue; + } + } + catch (Throwable error) { + if (STATE.compareAndSet(this, state, EMPTY_STATE)) { + Operators.onDiscard(cached, actual.currentContext()); + Operators.error(actual, error); + return null; + } + } + + + actual.onSubscribe(inner); + inner.complete(state.get()); + return null; + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class CoordinatorSubscriber implements InnerConsumer, State { + + final MonoCacheInvalidateIf main; + final Mono source; + + //no need for volatile: only used in onNext/onError/onComplete + boolean done = false; + + volatile Subscription upstream; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater UPSTREAM = + AtomicReferenceFieldUpdater.newUpdater(CoordinatorSubscriber.class, Subscription.class, "upstream"); + + + volatile CacheMonoSubscriber[] subscribers; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater SUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater(CoordinatorSubscriber.class, CacheMonoSubscriber[].class, "subscribers"); + + @SuppressWarnings("rawtypes") + private static final CacheMonoSubscriber[] COORDINATOR_DONE = new CacheMonoSubscriber[0]; + @SuppressWarnings("rawtypes") + private static final CacheMonoSubscriber[] COORDINATOR_INIT = new CacheMonoSubscriber[0]; + + @SuppressWarnings("unchecked") + CoordinatorSubscriber(MonoCacheInvalidateIf main, Mono source) { + this.main = main; + this.source = source; + this.subscribers = COORDINATOR_INIT; + } + + /** + * unused in this context as the {@link State} interface is only + * implemented for use in the main's STATE compareAndSet. + */ + @Nullable + @Override + public T get() { + throw new UnsupportedOperationException("coordinator State#get shouldn't be used"); + } + + /** + * unused in this context as the {@link State} interface is only + * implemented for use in the main's STATE compareAndSet. + */ + @Override + public void clear() { + //NO-OP + } + + final boolean add(CacheMonoSubscriber toAdd) { + for (; ; ) { + CacheMonoSubscriber[] a = subscribers; + if (a == COORDINATOR_DONE) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + CacheMonoSubscriber[] b = new CacheMonoSubscriber[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = toAdd; + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return true; + } + } + } + + final void remove(CacheMonoSubscriber toRemove) { + for (; ; ) { + CacheMonoSubscriber[] a = subscribers; + if (a == COORDINATOR_DONE || a == COORDINATOR_INIT) { + return; + } + int n = a.length; + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == toRemove) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + if (n == 1) { + if (SUBSCRIBERS.compareAndSet(this, a, COORDINATOR_DONE)) { + //cancel the subscription no matter what, at this point coordinator is done and cannot accept new subscribers + this.upstream.cancel(); + //only switch to EMPTY_STATE if the current state is this coordinator + STATE.compareAndSet(this.main, this, EMPTY_STATE); + return; + } + //else loop back + } + else { + CacheMonoSubscriber[] b = new CacheMonoSubscriber[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)) { + return; + } + //else loop back + } + } + } + + void delayedSubscribe() { + Subscription old = UPSTREAM.getAndSet(this, null); + if (old != null && old != Operators.cancelledSubscription()) { + old.cancel(); + } + source.subscribe(this); + } + + @Override + public void onSubscribe(Subscription s) { + //this only happens if there was at least one incoming subscriber + if (UPSTREAM.compareAndSet(this, null, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (main.state != this || done) { + Operators.onNextDropped(t, currentContext()); + return; + } + done = true; + //note the predicate is not applied upon reception of the value to be cached. only late subscribers will trigger revalidation. + State valueState = new ValueState<>(t); + if (STATE.compareAndSet(main, this, valueState)) { + for (@SuppressWarnings("unchecked") CacheMonoSubscriber inner : SUBSCRIBERS.getAndSet(this, COORDINATOR_DONE)) { + inner.complete(t); + } + } + } + + @Override + public void onError(Throwable t) { + if (main.state != this || done) { + Operators.onErrorDropped(t, currentContext()); + return; + } + if (STATE.compareAndSet(main, this, EMPTY_STATE)) { + for (@SuppressWarnings("unchecked") CacheMonoSubscriber inner : SUBSCRIBERS.getAndSet(this, COORDINATOR_DONE)) { + inner.onError(t); + } + } + } + + @Override + public void onComplete() { + if (done) { + done = false; + return; + } + if (STATE.compareAndSet(main, this, EMPTY_STATE)) { + for (@SuppressWarnings("unchecked") CacheMonoSubscriber inner : SUBSCRIBERS.getAndSet(this, COORDINATOR_DONE)) { + inner.onError(new NoSuchElementException("cacheInvalidateWhen expects a value, source completed empty")); + } + } + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + } + + static final class CacheMonoSubscriber extends Operators.MonoSubscriber { + + CoordinatorSubscriber coordinator; + + CacheMonoSubscriber(CoreSubscriber actual) { + super(actual); + } + + @Override + public void cancel() { + super.cancel(); + CoordinatorSubscriber coordinator = this.coordinator; + if (coordinator != null) { + coordinator.remove(this); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return coordinator.main; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCacheInvalidateWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCacheInvalidateWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCacheInvalidateWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2021 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.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.publisher.MonoCacheInvalidateIf.State; +import reactor.util.Logger; +import reactor.util.Loggers; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +import static reactor.core.publisher.MonoCacheInvalidateIf.EMPTY_STATE; + +/** + * A caching operator that uses a companion {@link Mono} as a trigger to invalidate the cached value. + * Subscribers accumulated between the upstream subscription is triggered and the actual value is received and stored + * don't get impacted by the generated trigger and will immediately receive the cached value. + * If after that the trigger completes immediately, the next incoming subscriber will lead to a resubscription to the + * source and a new caching cycle. + * + * @author Simon Baslé + */ +final class MonoCacheInvalidateWhen extends InternalMonoOperator { + + private static final Logger LOGGER = Loggers.getLogger(MonoCacheInvalidateWhen.class); + + final Function> invalidationTriggerGenerator; + @Nullable + final Consumer invalidateHandler; + + volatile State state; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater STATE = + AtomicReferenceFieldUpdater.newUpdater(MonoCacheInvalidateWhen.class, State.class, "state"); + + MonoCacheInvalidateWhen(Mono source, Function> invalidationTriggerGenerator, + @Nullable Consumer invalidateHandler) { + super(source); + this.invalidationTriggerGenerator = Objects.requireNonNull(invalidationTriggerGenerator, "invalidationTriggerGenerator"); + this.invalidateHandler = invalidateHandler; + @SuppressWarnings("unchecked") + State state = (State) EMPTY_STATE; //from MonoCacheInvalidateIf + this.state = state; + } + + boolean compareAndInvalidate(State expected) { + if (STATE.compareAndSet(this, expected, EMPTY_STATE)) { + if (expected instanceof MonoCacheInvalidateIf.ValueState) { + LOGGER.trace("invalidated {}", expected.get()); + safeInvalidateHandler(expected.get()); + } + return true; + } + return false; + } + + void invalidate() { + @SuppressWarnings("unchecked") + State oldState = STATE.getAndSet(this, EMPTY_STATE); + if (oldState instanceof MonoCacheInvalidateIf.ValueState) { + LOGGER.trace("invalidated {}", oldState.get()); + safeInvalidateHandler(oldState.get()); + } + } + + void safeInvalidateHandler(@Nullable T value) { + if (value != null && this.invalidateHandler != null) { + try { + this.invalidateHandler.accept(value); + } + catch (Throwable invalidateHandlerError) { + LOGGER.warnOrDebug( + isVerbose -> isVerbose ? "Failed to apply invalidate handler on value " + value : "Failed to apply invalidate handler", + invalidateHandlerError); + } + } + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CacheMonoSubscriber inner = new CacheMonoSubscriber<>(actual); + actual.onSubscribe(inner); + for(;;) { + State state = this.state; + if (state == EMPTY_STATE || state instanceof CoordinatorSubscriber) { + boolean subscribe = false; + CoordinatorSubscriber coordinator; + if (state == EMPTY_STATE) { + coordinator = new CoordinatorSubscriber<>(this); + if (!STATE.compareAndSet(this, EMPTY_STATE, coordinator)) { + continue; + } + subscribe = true; + } + else { + coordinator = (CoordinatorSubscriber) state; + } + + if (coordinator.add(inner)) { + if (inner.isCancelled()) { + coordinator.remove(inner); + } + else { + inner.coordinator = coordinator; + } + + if (subscribe) { + source.subscribe(coordinator); + } + return null; + } + } + else { + //state is an actual signal, cached + inner.complete(state.get()); + return null; + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class CoordinatorSubscriber implements InnerConsumer, State { + + final MonoCacheInvalidateWhen main; + + Subscription subscription; + + volatile CacheMonoSubscriber[] subscribers; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater SUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater(CoordinatorSubscriber.class, CacheMonoSubscriber[].class, "subscribers"); + + @SuppressWarnings("rawtypes") + private static final CacheMonoSubscriber[] COORDINATOR_DONE = new CacheMonoSubscriber[0]; + @SuppressWarnings("rawtypes") + private static final CacheMonoSubscriber[] COORDINATOR_INIT = new CacheMonoSubscriber[0]; + + @SuppressWarnings("unchecked") + CoordinatorSubscriber(MonoCacheInvalidateWhen main) { + this.main = main; + this.subscribers = COORDINATOR_INIT; + } + + /** + * unused in this context as the {@link State} interface is only + * implemented for use in the main's STATE compareAndSet. + */ + @Nullable + @Override + public T get() { + throw new UnsupportedOperationException("coordinator State#get shouldn't be used"); + } + + /** + * unused in this context as the {@link State} interface is only + * implemented for use in the main's STATE compareAndSet. + */ + @Override + public void clear() { + //NO-OP + } + + final boolean add(CacheMonoSubscriber toAdd) { + for (; ; ) { + CacheMonoSubscriber[] a = subscribers; + if (a == COORDINATOR_DONE) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + CacheMonoSubscriber[] b = new CacheMonoSubscriber[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = toAdd; + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return true; + } + } + } + + final void remove(CacheMonoSubscriber toRemove) { + for (; ; ) { + CacheMonoSubscriber[] a = subscribers; + if (a == COORDINATOR_DONE || a == COORDINATOR_INIT) { + return; + } + int n = a.length; + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == toRemove) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + if (n == 1) { + if (SUBSCRIBERS.compareAndSet(this, a, COORDINATOR_DONE)) { + if (main.compareAndInvalidate(this)) { + this.subscription.cancel(); + } + return; + } + //else loop back + } + else { + CacheMonoSubscriber[] b = new CacheMonoSubscriber[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)) { + return; + } + //else loop back + } + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.subscription, s)) { + this.subscription = s; + s.request(Long.MAX_VALUE); + } + } + + boolean cacheLoadFailure(State expected, Throwable failure) { + if (STATE.compareAndSet(main, expected, EMPTY_STATE)) { + for (@SuppressWarnings("unchecked") CacheMonoSubscriber inner : SUBSCRIBERS.getAndSet(this, COORDINATOR_DONE)) { + inner.onError(failure); + } + //no need to invalidate, EMPTY_STATE swap above is equivalent for our purpose + return true; + } + return false; + } + + void cacheLoad(T value) { + State valueState = new MonoCacheInvalidateIf.ValueState<>(value); + if (STATE.compareAndSet(main, this, valueState)) { + Mono invalidateTrigger = null; + try { + invalidateTrigger = Objects.requireNonNull( + main.invalidationTriggerGenerator.apply(value), + "invalidationTriggerGenerator produced a null trigger"); + } + catch (Throwable generatorError) { + if (cacheLoadFailure(valueState, generatorError)) { + main.safeInvalidateHandler(value); + } + return; + } + + for (@SuppressWarnings("unchecked") CacheMonoSubscriber inner : SUBSCRIBERS.getAndSet(this, COORDINATOR_DONE)) { + inner.complete(value); + } + invalidateTrigger.subscribe(new TriggerSubscriber(this.main)); + } + } + + @Override + public void onNext(T t) { + if (main.state != this) { + Operators.onNextDroppedMulticast(t, subscribers); + return; + } + cacheLoad(t); + } + + @Override + public void onError(Throwable t) { + if (main.state != this) { + Operators.onErrorDroppedMulticast(t, subscribers); + return; + } + cacheLoadFailure(this, t); + } + + @Override + public void onComplete() { + if (main.state == this) { + //completed empty + cacheLoadFailure(this, new NoSuchElementException("cacheInvalidateWhen expects a value, source completed empty")); + } + //otherwise, main.state MUST be a ValueState at this point + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + } + + static final class CacheMonoSubscriber extends Operators.MonoSubscriber { + + CoordinatorSubscriber coordinator; + + CacheMonoSubscriber(CoreSubscriber actual) { + super(actual); + } + + @Override + public void cancel() { + super.cancel(); + CoordinatorSubscriber coordinator = this.coordinator; + if (coordinator != null) { + coordinator.remove(this); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return coordinator.main; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + } + + + static final class TriggerSubscriber implements InnerConsumer { + + final MonoCacheInvalidateWhen main; + + TriggerSubscriber(MonoCacheInvalidateWhen main) { + this.main = main; + } + + @Override + public void onSubscribe(Subscription s) { + s.request(1); + } + + @Override + public void onNext(Void unused) { + //NO-OP + } + + @Override + public void onError(Throwable t) { + LOGGER.debug("Invalidation triggered by onError(" + t + ")"); + main.invalidate(); + } + + @Override + public void onComplete() { + main.invalidate(); + } + + @Override + public Context currentContext() { + return Context.empty(); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return main; + //this is an approximation of TERMINATED, the trigger should only be active AFTER an actual value has been set as STATE + if (key == Attr.TERMINATED) return main.state == EMPTY_STATE || main.state instanceof CoordinatorSubscriber; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + if (key == Attr.PREFETCH) return 1; + return null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCacheTime.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCacheTime.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCacheTime.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2017-2021 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.TimeUnit; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.scheduler.Scheduler; +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; + +/** + * An operator that caches the value from a source Mono with a TTL, after which the value + * expires and the next subscription will trigger a new source subscription. + * + * @author Simon Baslé + */ +class MonoCacheTime extends InternalMonoOperator implements Runnable { + + private static final Duration DURATION_INFINITE = Duration.ofMillis(Long.MAX_VALUE); + + private static final Logger LOGGER = Loggers.getLogger(MonoCacheTime.class); + + final Function, Duration> ttlGenerator; + final Scheduler clock; + + volatile Signal state; + static final AtomicReferenceFieldUpdater STATE = + AtomicReferenceFieldUpdater.newUpdater(MonoCacheTime.class, Signal.class, "state"); + + static final Signal EMPTY = new ImmutableSignal<>(Context.empty(), SignalType.ON_NEXT, null, null, null); + + MonoCacheTime(Mono source, Duration ttl, Scheduler clock) { + super(source); + Objects.requireNonNull(ttl, "ttl must not be null"); + Objects.requireNonNull(clock, "clock must not be null"); + + this.ttlGenerator = ignoredSignal -> ttl; + this.clock = clock; + @SuppressWarnings("unchecked") + Signal state = (Signal) EMPTY; + this.state = state; + } + + MonoCacheTime(Mono source) { + this(source, sig -> DURATION_INFINITE, Schedulers.immediate()); + } + + MonoCacheTime(Mono source, Function, Duration> ttlGenerator, + Scheduler clock) { + super(source); + this.ttlGenerator = ttlGenerator; + this.clock = clock; + @SuppressWarnings("unchecked") + Signal state = (Signal) EMPTY; + this.state = state; + } + + MonoCacheTime(Mono source, + Function valueTtlGenerator, + Function errorTtlGenerator, + Supplier emptyTtlGenerator, + Scheduler clock) { + super(source); + Objects.requireNonNull(valueTtlGenerator, "valueTtlGenerator must not be null"); + Objects.requireNonNull(errorTtlGenerator, "errorTtlGenerator must not be null"); + Objects.requireNonNull(emptyTtlGenerator, "emptyTtlGenerator must not be null"); + Objects.requireNonNull(clock, "clock must not be null"); + + this.ttlGenerator = sig -> { + if (sig.isOnNext()) return valueTtlGenerator.apply(sig.get()); + if (sig.isOnError()) return errorTtlGenerator.apply(sig.getThrowable()); + return emptyTtlGenerator.get(); + }; + this.clock = clock; + @SuppressWarnings("unchecked") + Signal emptyState = (Signal) EMPTY; + this.state = emptyState; + } + + public void run() { + LOGGER.debug("expired {}", state); + @SuppressWarnings("unchecked") + Signal emptyState = (Signal) EMPTY; + state = emptyState; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CacheMonoSubscriber inner = new CacheMonoSubscriber<>(actual); + actual.onSubscribe(inner); + for(;;){ + Signal state = this.state; + if (state == EMPTY || state instanceof CoordinatorSubscriber) { + boolean subscribe = false; + CoordinatorSubscriber coordinator; + if (state == EMPTY) { + coordinator = new CoordinatorSubscriber<>(this); + if (!STATE.compareAndSet(this, EMPTY, coordinator)) { + continue; + } + subscribe = true; + } + else { + coordinator = (CoordinatorSubscriber) state; + } + + if (coordinator.add(inner)) { + if (inner.isCancelled()) { + coordinator.remove(inner); + } + else { + inner.coordinator = coordinator; + } + + if (subscribe) { + source.subscribe(coordinator); + } + break; + } + } + else { + //state is an actual signal, cached + if (state.isOnNext()) { + inner.complete(state.get()); + } + else if (state.isOnComplete()) { + inner.onComplete(); + } + else { + inner.onError(state.getThrowable()); + } + break; + } + } + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class CoordinatorSubscriber implements InnerConsumer, Signal { + + final MonoCacheTime main; + + volatile Subscription subscription; + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(CoordinatorSubscriber.class, Subscription.class, "subscription"); + + volatile Operators.MonoSubscriber[] subscribers; + static final AtomicReferenceFieldUpdater SUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater(CoordinatorSubscriber.class, Operators.MonoSubscriber[].class, "subscribers"); + + @SuppressWarnings("unchecked") + CoordinatorSubscriber(MonoCacheTime main) { + this.main = main; + this.subscribers = EMPTY; + } + + /** + * unused in this context as the {@link Signal} interface is only + * implemented for use in the main's STATE compareAndSet. + */ + @Override + public Throwable getThrowable() { + throw new UnsupportedOperationException("illegal signal use"); + } + + /** + * unused in this context as the {@link Signal} interface is only + * implemented for use in the main's STATE compareAndSet. + */ + @Override + public Subscription getSubscription() { + throw new UnsupportedOperationException("illegal signal use"); + } + + /** + * unused in this context as the {@link Signal} interface is only + * implemented for use in the main's STATE compareAndSet. + */ + @Override + public T get() { + throw new UnsupportedOperationException("illegal signal use"); + } + + /** + * unused in this context as the {@link Signal} interface is only + * implemented for use in the main's STATE compareAndSet. + */ + @Override + public SignalType getType() { + throw new UnsupportedOperationException("illegal signal use"); + } + + /** + * unused in this context as the {@link Signal} interface is only + * implemented for use in the main's STATE compareAndSet. + */ + @Override + public ContextView getContextView() { + throw new UnsupportedOperationException("illegal signal use: getContextView"); + } + + final boolean add(Operators.MonoSubscriber toAdd) { + for (; ; ) { + Operators.MonoSubscriber[] a = subscribers; + if (a == TERMINATED) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + Operators.MonoSubscriber[] b = new Operators.MonoSubscriber[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = toAdd; + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return true; + } + } + } + + final void remove(Operators.MonoSubscriber toRemove) { + for (; ; ) { + Operators.MonoSubscriber[] 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] == toRemove) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + Operators.MonoSubscriber[] b; + if (n == 1) { + b = EMPTY; + } + else { + b = new Operators.MonoSubscriber[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)) { + //no particular cleanup here for the EMPTY case, we don't cancel the + // source because a new subscriber could come in before the coordinator + // is terminated. + + return; + } + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(subscription, s)) { + subscription = s; + s.request(Long.MAX_VALUE); + } + } + + @SuppressWarnings("unchecked") + private void signalCached(Signal signal) { + Signal signalToPropagate = signal; + if (STATE.compareAndSet(main, this, signal)) { + Duration ttl = null; + try { + ttl = main.ttlGenerator.apply(signal); + } + catch (Throwable generatorError) { + signalToPropagate = Signal.error(generatorError); + STATE.set(main, signalToPropagate); + if (signal.isOnError()) { + //noinspection ThrowableNotThrown + Exceptions.addSuppressed(generatorError, signal.getThrowable()); + } + } + + if (ttl != null) { + if (ttl.isZero()) { + //immediate cache clear + main.run(); + } + else if (!ttl.equals(DURATION_INFINITE)) { + main.clock.schedule(main, ttl.toNanos(), TimeUnit.NANOSECONDS); + } + //else TTL is Long.MAX_VALUE, schedule nothing but still cache + } + else { + //error during TTL generation, signal != updatedSignal, aka dropped + if (signal.isOnNext()) { + Operators.onNextDropped(signal.get(), currentContext()); + } + //if signal.isOnError(), avoid dropping the error. it is not really dropped but already suppressed + //in all cases, unless nextDropped hook throws, immediate cache clear + main.run(); + } + } + + for (Operators.MonoSubscriber inner : SUBSCRIBERS.getAndSet(this, TERMINATED)) { + if (signalToPropagate.isOnNext()) { + inner.complete(signalToPropagate.get()); + } + else if (signalToPropagate.isOnError()) { + inner.onError(signalToPropagate.getThrowable()); + } + else { + inner.onComplete(); + } + } + } + + @Override + public void onNext(T t) { + if (main.state != this) { + Operators.onNextDroppedMulticast(t, subscribers); + return; + } + signalCached(Signal.next(t)); + } + + @Override + public void onError(Throwable t) { + if (main.state != this) { + Operators.onErrorDroppedMulticast(t, subscribers); + return; + } + signalCached(Signal.error(t)); + } + + @Override + public void onComplete() { + signalCached(Signal.complete()); + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + private static final Operators.MonoSubscriber[] TERMINATED = new Operators.MonoSubscriber[0]; + private static final Operators.MonoSubscriber[] EMPTY = new Operators.MonoSubscriber[0]; + } + + static final class CacheMonoSubscriber extends Operators.MonoSubscriber { + + CoordinatorSubscriber coordinator; + + CacheMonoSubscriber(CoreSubscriber actual) { + super(actual); + } + + @Override + public void cancel() { + super.cancel(); + CoordinatorSubscriber coordinator = this.coordinator; + if (coordinator != null) { + coordinator.remove(this); + } + } + + @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/MonoCallable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCallable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCallable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016-2021 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.Callable; + +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. + * + * @param the returned value type + * @see Reactive-Streams-Commons + */ +final class MonoCallable extends Mono + implements Callable, Fuseable, SourceProducer { + + final Callable callable; + + MonoCallable(Callable callable) { + this.callable = Objects.requireNonNull(callable, "callable"); + } + + @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())); + } + + } + + @Override + @Nullable + public T block() { + //duration is ignored below + return block(Duration.ZERO); + } + + @Override + @Nullable + public T block(Duration m) { + try { + return callable.call(); + } + catch (Throwable e) { + throw Exceptions.propagate(e); + } + } + + @Override + @Nullable + public T call() throws Exception { + return callable.call(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCallableOnAssembly.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCallableOnAssembly.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCallableOnAssembly.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2016-2021 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.concurrent.Callable; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; +import reactor.util.annotation.Nullable; + +/** + * Captures the current stacktrace when this publisher is created and makes it + * available/visible for debugging purposes from the inner Subscriber. + *

+ * Note that getting a stacktrace is a costly operation. + *

+ * The operator sanitizes the stacktrace and removes noisy entries such as:

    + *
  • java.lang.Thread entries
  • method references with source line of 1 (bridge + * methods)
  • Tomcat worker thread entries
  • JUnit setup
+ * + * @param the value type passing through + * + * @see https://github.com/reactor/reactive-streams-commons + */ +final class MonoCallableOnAssembly extends InternalMonoOperator + implements Callable, AssemblyOp { + + final AssemblySnapshot stacktrace; + + MonoCallableOnAssembly(Mono source, AssemblySnapshot stacktrace) { + super(source); + this.stacktrace = stacktrace; + } + + @Override + @Nullable + public T block() { + //duration is ignored below + return block(Duration.ZERO); + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + public T block(Duration timeout) { + try { + return ((Callable) source).call(); + } + catch (Throwable e) { + throw Exceptions.propagate(e); + } + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof Fuseable.ConditionalSubscriber) { + Fuseable.ConditionalSubscriber + cs = (Fuseable.ConditionalSubscriber) actual; + return new FluxOnAssembly.OnAssemblyConditionalSubscriber<>(cs, + stacktrace, + source, + this); + } + else { + return new FluxOnAssembly.OnAssemblySubscriber<>(actual, stacktrace, source, this); + } + } + + @SuppressWarnings("unchecked") + @Override + @Nullable + public T call() throws Exception { + return ((Callable) source).call(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL_METADATA) return !stacktrace.isCheckpoint; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public String stepName() { + return stacktrace.operatorAssemblyInformation(); + } + + @Override + public String toString() { + return stacktrace.operatorAssemblyInformation(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCancelOn.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCancelOn.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCancelOn.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-2021 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.scheduler.Scheduler; + +final class MonoCancelOn extends InternalMonoOperator { + + final Scheduler scheduler; + + MonoCancelOn(Mono source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxCancelOn.CancelSubscriber(actual, scheduler); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCollect.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCollect.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCollect.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2016-2021 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.Collection; +import java.util.Objects; +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; +import reactor.util.context.Context; + +/** + * Collects the values of the source sequence into a container returned by + * a supplier and a collector action working on the container and the current source + * value. + * + * @param the source value type + * @param the container value type + * + * @see Reactive-Streams-Commons + */ +final class MonoCollect extends MonoFromFluxOperator + implements Fuseable { + + final Supplier supplier; + + final BiConsumer action; + + MonoCollect(Flux source, + Supplier supplier, + BiConsumer action) { + super(source); + this.supplier = Objects.requireNonNull(supplier, "supplier"); + this.action = Objects.requireNonNull(action); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + R container = Objects.requireNonNull(supplier.get(), + "The supplier returned a null container"); + + return new CollectSubscriber<>(actual, action, container); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class CollectSubscriber extends Operators.MonoSubscriber { + + final BiConsumer action; + + R container; + + Subscription s; + + boolean done; + + CollectSubscriber(CoreSubscriber actual, + BiConsumer action, + R container) { + super(actual); + this.action = action; + this.container = container; + } + + @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 { + action.accept(c, t); + } + catch (Throwable e) { + Context ctx = actual.currentContext(); + Operators.onDiscard(t, ctx); + onError(Operators.onOperatorError(this, e, t, ctx)); + } + return; + } + } + Operators.onDiscard(t, actual.currentContext()); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + R c; + synchronized (this) { + c = container; + container = null; + } + discard(c); + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + R c; + synchronized (this) { + c = container; + container = null; + } + if (c != null) { + complete(c); + } + } + + @Override + protected void discard(R v) { + if (v instanceof Collection) { + Collection c = (Collection) v; + Operators.onDiscardMultiple(c, actual.currentContext()); + } + else { + super.discard(v); + } + } + + @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 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCollectList.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCollectList.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2016-2021 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.ArrayList; +import java.util.List; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Buffers all values from the source Publisher and emits it as a single List. + * + * @param the source value type + */ +final class MonoCollectList extends MonoFromFluxOperator> implements Fuseable { + + MonoCollectList(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new MonoCollectListSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class MonoCollectListSubscriber extends Operators.MonoSubscriber> { + + List list; + + Subscription s; + + boolean done; + + MonoCollectListSubscriber(CoreSubscriber> actual) { + super(actual); + //not this is not thread safe so concurrent discarding multiple + add might fail with ConcurrentModificationException + this.list = new ArrayList<>(); + } + + @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; + } + List l; + synchronized (this) { + l = list; + if (l != null) { + l.add(t); + return; + } + } + Operators.onDiscard(t, actual.currentContext()); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + List l; + synchronized (this) { + l = list; + list = null; + } + discard(l); + actual.onError(t); + } + + @Override + public void onComplete() { + if(done) { + return; + } + done = true; + List l; + synchronized (this) { + l = list; + list = null; + } + if (l != null) { + complete(l); + } + } + + @Override + protected void discard(List v) { + Operators.onDiscardMultiple(v, actual.currentContext()); + } + + @Override + public void cancel() { + int state; + 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; + } + } + if (l != null) { + discard(l); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCompletionStage.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCompletionStage.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCompletionStage.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016-2021 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.CancellationException; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.context.Context; + +/** + * Emits the value or error produced by the wrapped CompletionStage. + *

+ * Note that if Subscribers cancel their subscriptions, the CompletionStage + * is not cancelled. + * + * @param the value type + */ +final class MonoCompletionStage extends Mono + implements Fuseable, Scannable { + + final CompletionStage future; + + MonoCompletionStage(CompletionStage future) { + this.future = Objects.requireNonNull(future, "future"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Operators.MonoSubscriber + sds = new Operators.MonoSubscriber<>(actual); + + actual.onSubscribe(sds); + + if (sds.isCancelled()) { + return; + } + + future.whenComplete((v, e) -> { + if (sds.isCancelled()) { + //nobody is interested in the Mono anymore, don't risk dropping errors + Context ctx = sds.currentContext(); + if (e == null || e instanceof CancellationException) { + //we discard any potential value and ignore Future cancellations + Operators.onDiscard(v, 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); + } + + return; + } + try { + if (e instanceof CompletionException) { + actual.onError(e.getCause()); + } + else if (e != null) { + actual.onError(e); + } + else if (v != null) { + sds.complete(v); + } + else { + actual.onComplete(); + } + } + catch (Throwable e1) { + Operators.onErrorDropped(e1, actual.currentContext()); + throw Exceptions.bubble(e1); + } + }); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoContextWrite.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoContextWrite.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoContextWrite.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.context.Context; + +final class MonoContextWrite extends InternalMonoOperator implements Fuseable { + + final Function doOnContext; + + MonoContextWrite(Mono source, + Function doOnContext) { + super(source); + this.doOnContext = Objects.requireNonNull(doOnContext, "doOnContext"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + Context c = doOnContext.apply(actual.currentContext()); + + return new FluxContextWrite.ContextWriteSubscriber<>(actual, c); + } + + @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/MonoCount.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCount.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCount.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2016-2021 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.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Counts the number of values in the source sequence. + * + * @param the source value type + * @see Reactive-Streams-Commons + * + */ +final class MonoCount extends MonoFromFluxOperator implements Fuseable { + + MonoCount(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new CountSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class CountSubscriber extends Operators.MonoSubscriber { + + 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; + + return super.scanUnsafe(key); + } + + @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); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + counter++; + } + + @Override + public void onComplete() { + complete(counter); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCreate.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCreate.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCreate.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2016-2021 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.AtomicBoolean; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; +import java.util.function.LongConsumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.publisher.FluxCreate.SinkDisposable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Wraps the downstream Subscriber into a single emission object and calls the given + * callback to produce a signal (a)synchronously. + * + * @param the value type + */ +final class MonoCreate extends Mono implements SourceProducer { + + static final Disposable TERMINATED = OperatorDisposables.DISPOSED; + static final Disposable CANCELLED = Disposables.disposed(); + + final Consumer> callback; + + MonoCreate(Consumer> callback) { + this.callback = callback; + } + + @Override + public void subscribe(CoreSubscriber actual) { + DefaultMonoSink emitter = new DefaultMonoSink<>(actual); + + actual.onSubscribe(emitter); + + try { + callback.accept(emitter); + } + catch (Throwable ex) { + emitter.error(Operators.onOperatorError(ex, actual.currentContext())); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + return null; + } + + static final class DefaultMonoSink extends AtomicBoolean + implements MonoSink, InnerProducer { + + final CoreSubscriber actual; + + volatile Disposable disposable; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater DISPOSABLE = + AtomicReferenceFieldUpdater.newUpdater(DefaultMonoSink.class, + Disposable.class, + "disposable"); + + volatile int state; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(DefaultMonoSink.class, "state"); + + volatile LongConsumer requestConsumer; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + REQUEST_CONSUMER = + AtomicReferenceFieldUpdater.newUpdater(DefaultMonoSink.class, + LongConsumer.class, + "requestConsumer"); + + T value; + + static final int NO_REQUEST_HAS_VALUE = 1; + static final int HAS_REQUEST_NO_VALUE = 2; + static final int HAS_REQUEST_HAS_VALUE = 3; + + DefaultMonoSink(CoreSubscriber actual) { + this.actual = actual; + } + + @Override + public Context currentContext() { + return this.actual.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) { + return state == HAS_REQUEST_HAS_VALUE || state == NO_REQUEST_HAS_VALUE || disposable == TERMINATED; + } + if (key == Attr.CANCELLED) { + return disposable == CANCELLED; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.ASYNC; + } + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public void success() { + if (isDisposed()) { + return; + } + if (STATE.getAndSet(this, HAS_REQUEST_HAS_VALUE) != HAS_REQUEST_HAS_VALUE) { + try { + actual.onComplete(); + } + finally { + disposeResource(false); + } + } + } + + @Override + public void success(@Nullable T value) { + if (value == null) { + success(); + return; + } + Disposable d = this.disposable; + if (d == CANCELLED) { + Operators.onDiscard(value, actual.currentContext()); + return; + } + else if (d == TERMINATED) { + Operators.onNextDropped(value, actual.currentContext()); + return; + } + for (; ; ) { + int s = state; + if (s == HAS_REQUEST_HAS_VALUE || s == NO_REQUEST_HAS_VALUE) { + Operators.onNextDropped(value, actual.currentContext()); + return; + } + if (s == HAS_REQUEST_NO_VALUE) { + if (STATE.compareAndSet(this, s, HAS_REQUEST_HAS_VALUE)) { + try { + actual.onNext(value); + actual.onComplete(); + } + catch (Throwable t) { + actual.onError(t); + } + finally { + disposeResource(false); + } + } else { + Operators.onNextDropped(value, actual.currentContext()); + } + return; + } + this.value = value; + if (STATE.compareAndSet(this, s, NO_REQUEST_HAS_VALUE)) { + return; + } + } + } + + @Override + public void error(Throwable e) { + if (isDisposed()) { + Operators.onOperatorError(e, actual.currentContext()); + return; + } + if (STATE.getAndSet(this, HAS_REQUEST_HAS_VALUE) != HAS_REQUEST_HAS_VALUE) { + try { + actual.onError(e); + } + finally { + disposeResource(false); + } + } + else { + Operators.onOperatorError(e, actual.currentContext()); + } + } + + @Override + public MonoSink onRequest(LongConsumer consumer) { + Objects.requireNonNull(consumer, "onRequest"); + if (!REQUEST_CONSUMER.compareAndSet(this, null, consumer)) { + throw new IllegalStateException( + "A consumer has already been assigned to consume requests"); + } + int s = this.state; + if (s == HAS_REQUEST_NO_VALUE || s == HAS_REQUEST_HAS_VALUE) { + consumer.accept(Long.MAX_VALUE); + } + return this; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public MonoSink onCancel(Disposable d) { + Objects.requireNonNull(d, "onCancel"); + SinkDisposable sd = new SinkDisposable(null, d); + if (!DISPOSABLE.compareAndSet(this, null, sd)) { + Disposable c = disposable; + if (c == CANCELLED) { + d.dispose(); + } + else if (c instanceof SinkDisposable) { + SinkDisposable current = (SinkDisposable) c; + if (current.onCancel == null) { + current.onCancel = d; + } + else { + d.dispose(); + } + } + } + return this; + } + + @Override + public MonoSink onDispose(Disposable d) { + Objects.requireNonNull(d, "onDispose"); + SinkDisposable sd = new SinkDisposable(d, null); + if (!DISPOSABLE.compareAndSet(this, null, sd)) { + Disposable c = disposable; + if (isDisposed()) { + d.dispose(); + } + else if (c instanceof SinkDisposable) { + SinkDisposable current = (SinkDisposable) c; + if (current.disposable == null) { + current.disposable = d; + } + else { + d.dispose(); + } + } + } + return this; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + LongConsumer consumer = requestConsumer; + if (consumer != null) { + consumer.accept(n); + } + for (; ; ) { + int s = state; + if (s == HAS_REQUEST_NO_VALUE || s == HAS_REQUEST_HAS_VALUE) { + return; + } + if (s == NO_REQUEST_HAS_VALUE) { + if (STATE.compareAndSet(this, s, HAS_REQUEST_HAS_VALUE)) { + try { + actual.onNext(value); + actual.onComplete(); + } + finally { + disposeResource(false); + } + } + return; + } + if (STATE.compareAndSet(this, s, HAS_REQUEST_NO_VALUE)) { + return; + } + } + } + } + + @Override + public void cancel() { + if (STATE.getAndSet(this, HAS_REQUEST_HAS_VALUE) != HAS_REQUEST_HAS_VALUE) { + T old = value; + value = null; + Operators.onDiscard(old, actual.currentContext()); + disposeResource(true); + } + } + + void disposeResource(boolean isCancel) { + Disposable target = isCancel ? CANCELLED : TERMINATED; + Disposable d = disposable; + if (d != TERMINATED && d != CANCELLED) { + d = DISPOSABLE.getAndSet(this, target); + if (d != null && d != TERMINATED && d != CANCELLED) { + if (isCancel && d instanceof SinkDisposable) { + ((SinkDisposable) d).cancel(); + } + d.dispose(); + } + } + } + + @Override + public String toString() { + return "MonoSink"; + } + + boolean isDisposed() { + Disposable d = disposable; + return d == CANCELLED || d == TERMINATED; + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoCurrentContext.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoCurrentContext.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoCurrentContext.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2016-2021 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.Scannable; +import reactor.util.context.Context; + +/** + * Materialize current {@link Context} from the subscribing flow + */ +@Deprecated +final class MonoCurrentContext extends Mono + implements Fuseable, Scannable { + + static final MonoCurrentContext INSTANCE = new MonoCurrentContext(); + + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + Context ctx = actual.currentContext(); + actual.onSubscribe(Operators.scalarSubscription(actual, ctx)); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDefaultIfEmpty.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDefaultIfEmpty.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDefaultIfEmpty.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; + +/** + * Emits a default value if the wrapped Mono is empty. + * @param the value type + */ +final class MonoDefaultIfEmpty extends InternalMonoOperator { + final T defaultValue; + + MonoDefaultIfEmpty(Mono source, T defaultValue) { + super(source); + this.defaultValue = Objects.requireNonNull(defaultValue, "defaultValue"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxDefaultIfEmpty.DefaultIfEmptySubscriber<>(actual, defaultValue); + } + + @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/MonoDefer.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDefer.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDefer.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016-2021 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.Supplier; + +import reactor.core.CoreSubscriber; + +/** + * Defers the creation of the actual Publisher the Subscriber will be subscribed to. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class MonoDefer extends Mono implements SourceProducer { + + final Supplier> supplier; + + MonoDefer(Supplier> supplier) { + this.supplier = Objects.requireNonNull(supplier, "supplier"); + } + + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + Mono p; + + try { + p = Objects.requireNonNull(supplier.get(), + "The Mono returned by the supplier is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + + p.subscribe(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDeferContextual.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDeferContextual.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDeferContextual.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +/** + * Defers the creation of the actual Publisher the Subscriber will be subscribed to. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class MonoDeferContextual extends Mono implements SourceProducer { + + final Function> contextualMonoFactory; + + MonoDeferContextual(Function> contextualMonoFactory) { + this.contextualMonoFactory = Objects.requireNonNull(contextualMonoFactory, "contextualMonoFactory"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Mono p; + + Context ctx = actual.currentContext(); + try { + p = Objects.requireNonNull(contextualMonoFactory.apply(ctx), + "The Mono returned by the contextualMonoFactory is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, ctx)); + return; + } + + p.subscribe(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; //no particular key to be represented, still useful in hooks + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDelay.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDelay.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDelay.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +/** + * Emits a single 0L value delayed by some time amount with a help of + * a ScheduledExecutorService instance or a generic function callback that + * wraps other form of async-delayed execution of tasks. + * @see Reactive-Streams-Commons + */ +final class MonoDelay extends Mono implements Scannable, SourceProducer { + + final Scheduler timedScheduler; + + final long delay; + + final TimeUnit unit; + + MonoDelay(long delay, TimeUnit unit, Scheduler timedScheduler) { + this.delay = delay; + this.unit = Objects.requireNonNull(unit, "unit"); + this.timedScheduler = Objects.requireNonNull(timedScheduler, "timedScheduler"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + boolean failOnBackpressure = actual.currentContext().getOrDefault(CONTEXT_OPT_OUT_NOBACKPRESSURE, false) == Boolean.TRUE; + + MonoDelayRunnable r = new MonoDelayRunnable(actual, failOnBackpressure); + + actual.onSubscribe(r); + + try { + r.setCancel(timedScheduler.schedule(r, delay, unit)); + } + catch (RejectedExecutionException ree) { + if(!MonoDelayRunnable.wasCancelled(r.state)) { + actual.onError(Operators.onRejectedExecution(ree, r, null, null, + actual.currentContext())); + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return timedScheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return null; + } + + static final class MonoDelayRunnable implements Runnable, InnerProducer { + final CoreSubscriber actual; + final boolean failOnBackpressure; + + Disposable cancel; + + volatile int state; + static final AtomicIntegerFieldUpdater STATE = AtomicIntegerFieldUpdater.newUpdater(MonoDelayRunnable.class, "state"); + + /** This bit marks the subscription as cancelled */ + static final byte FLAG_CANCELLED = 0b0100_0_000; + /** This bit marks the subscription as requested (once) */ + static final byte FLAG_REQUESTED = 0b0010_0_000; + /** This bit indicates that a request happened before the delay timer was done (utility, especially in tests) */ + static final byte FLAG_REQUESTED_EARLY = 0b0001_0_000; + /** This bit indicates that a Disposable was set corresponding to the timer */ + static final byte FLAG_CANCEL_SET = 0b0000_0_001; + /** This bit indicates that the timer has expired */ + static final byte FLAG_DELAY_DONE = 0b0000_0_010; + /** This bit indicates that, following expiry of the timer AND request, onNext(0L) has been propagated downstream */ + static final byte FLAG_PROPAGATED = 0b0000_0_100; + + MonoDelayRunnable(CoreSubscriber actual, boolean failOnBackpressure) { + this.actual = actual; + this.failOnBackpressure = failOnBackpressure; + } + + /** + * Mark that the cancel {@link Disposable} was set from the {@link java.util.concurrent.Future}. + * Immediately returns if it {@link #wasCancelled(int) was cancelled} or if + * {@link #wasCancelFutureSet(int) the disposable was already set}. + */ + static int markCancelFutureSet(MonoDelayRunnable instance) { + for (;;) { + int state = instance.state; + if (wasCancelled(state) || wasCancelFutureSet(state)) { + return state; + } + + if (STATE.compareAndSet(instance, state, state | FLAG_CANCEL_SET)) { + return state; + } + } + } + + /** + * @return true if the {@link #FLAG_CANCEL_SET} bit is set on the given state + */ + static boolean wasCancelFutureSet(int state) { + return (state & FLAG_CANCEL_SET) == FLAG_CANCEL_SET; + } + + /** + * Mark the subscriber has been cancelled. Immediately returns if it + * {@link #wasCancelled(int) was already cancelled}. + */ + static int markCancelled(MonoDelayRunnable instance) { + for (;;) { + int state = instance.state; + if (wasCancelled(state) || wasPropagated(state)) { + return state; + } + + if (STATE.compareAndSet(instance, state, state | FLAG_CANCELLED)) { + return state; + } + } + } + + /** + * @return true if the {@link #FLAG_CANCELLED} bit is set on the given state + */ + static boolean wasCancelled(int state) { + return (state & FLAG_CANCELLED) == FLAG_CANCELLED; + } + + /** + * Mark the delay has run out. Immediately returns if it + * {@link #wasCancelled(int) was already cancelled} or already + * {@link #wasDelayDone(int) marked as done}. + */ + static int markDelayDone(MonoDelayRunnable instance) { + for (;;) { + int state = instance.state; + if (wasCancelled(state) || wasDelayDone(state)) { + return state; + } + + if (STATE.compareAndSet(instance, state, state | FLAG_DELAY_DONE)) { + return state; + } + } + } + + /** + * @return true if the {@link #FLAG_DELAY_DONE} bit is set on the given state + */ + static boolean wasDelayDone(int state) { + return (state & FLAG_DELAY_DONE) == FLAG_DELAY_DONE; + } + + /** + * Mark the operator has been requested. Immediately returns if it + * {@link #wasCancelled(int) was already cancelled} or already + * {@link #wasRequested(int) requested}. Also sets the {@link #FLAG_REQUESTED_EARLY} + * flag if the delay was already done when request happened. + */ + static int markRequested(MonoDelayRunnable instance) { + for (;;) { + int state = instance.state; + if (wasCancelled(state) || wasRequested(state)) { + return state; + } + int newFlag = FLAG_REQUESTED; + if (!wasDelayDone(state)) { + newFlag = newFlag | FLAG_REQUESTED_EARLY; + } + + if (STATE.compareAndSet(instance, state, state | newFlag)) { + return state; + } + } + } + + /** + * @return true if the {@link #FLAG_REQUESTED} bit is set on the given state + */ + static boolean wasRequested(int state) { + return (state & FLAG_REQUESTED) == FLAG_REQUESTED; + } + + /** + * Mark the operator has emitted the tick. Immediately returns if it + * {@link #wasCancelled(int) was already cancelled}. + */ + static int markPropagated(MonoDelayRunnable instance) { + for (;;) { + int state = instance.state; + if (wasCancelled(state)) { + return state; + } + + if (STATE.compareAndSet(instance, state, state | FLAG_PROPAGATED)) { + return state; + } + } + } + + /** + * @return true if the {@link #FLAG_PROPAGATED} bit is set on the given state + */ + static boolean wasPropagated(int state) { + return (state & FLAG_PROPAGATED) == FLAG_PROPAGATED; + } + + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return wasDelayDone(this.state) && wasRequested(this.state); + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return wasRequested(this.state) ? 1L : 0L; + if (key == Attr.CANCELLED) return wasCancelled(this.state); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + void setCancel(Disposable cancel) { + Disposable c = this.cancel; + this.cancel = cancel; + int previousState = markCancelFutureSet(this); + if (wasCancelFutureSet(previousState)) { + if (c != null) { + c.dispose(); + } + return; + } + if (wasCancelled(previousState)) { + cancel.dispose(); + } + } + + private void propagateDelay() { + int previousState = markPropagated(this); + if (wasCancelled(previousState)) { + return; + } + try { + actual.onNext(0L); + actual.onComplete(); + } + catch (Throwable t){ + actual.onError(Operators.onOperatorError(t, actual.currentContext())); + } + } + + @Override + public void run() { + int previousState = markDelayDone(this); + if (wasCancelled(previousState) || wasDelayDone(previousState)) { + return; + } + if (wasRequested(previousState)) { + propagateDelay(); + } + else if (failOnBackpressure) { + actual.onError(Exceptions.failWithOverflow("Could not emit value due to lack of requests")); + } + } + + @Override + public void cancel() { + int previousState = markCancelled(this); + if (wasCancelled(previousState) || wasPropagated(previousState)) { + //ignore + return; + } + if (wasCancelFutureSet(previousState)) { + this.cancel.dispose(); + } + //otherwise having marked cancelled will trigger immediate disposal upon setCancel + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + int previousState = markRequested(this); + if (wasCancelled(previousState) || wasRequested(previousState)) { + return; + } + if (wasDelayDone(previousState) && !failOnBackpressure) { + propagateDelay(); + } + } + } + } + + static final String CONTEXT_OPT_OUT_NOBACKPRESSURE = "reactor.core.publisher.MonoDelay.failOnBackpressure"; +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDelayElement.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDelayElement.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDelayElement.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +/** + * Emits the first value emitted by a given source publisher, delayed by some time amount + * with the help of a ScheduledExecutorService instance or a generic function callback that + * wraps other form of async-delayed execution of tasks. + * + * @see Reactive-Streams-Commons + * @author Simon Baslé + * TODO : Review impl + */ +final class MonoDelayElement extends InternalMonoOperator { + + final Scheduler timedScheduler; + + final long delay; + + final TimeUnit unit; + + MonoDelayElement(Mono source, long delay, TimeUnit unit, Scheduler timedScheduler) { + super(source); + this.delay = delay; + this.unit = Objects.requireNonNull(unit, "unit"); + this.timedScheduler = Objects.requireNonNull(timedScheduler, "timedScheduler"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new DelayElementSubscriber<>(actual, timedScheduler, delay, unit); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return timedScheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return super.scanUnsafe(key); + } + + static final class DelayElementSubscriber extends Operators.MonoSubscriber { + + final long delay; + final Scheduler scheduler; + final TimeUnit unit; + + Subscription s; + + volatile Disposable task; + boolean done; + + DelayElementSubscriber(CoreSubscriber actual, Scheduler scheduler, + long delay, TimeUnit unit) { + super(actual); + this.scheduler = scheduler; + this.delay = delay; + this.unit = unit; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + 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); + } + + @Override + public void cancel() { + super.cancel(); + if (task != null) { + task.dispose(); + } + if (s != Operators.cancelledSubscription()) { + s.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.onNextDropped(t, actual.currentContext()); + return; + } + this.done = true; + try { + this.task = scheduler.schedule(() -> complete(t), delay, unit); + } + catch (RejectedExecutionException ree) { + actual.onError(Operators.onRejectedExecution(ree, this, null, t, + actual.currentContext())); + return; + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + this.done = true; + actual.onComplete(); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + this.done = true; + actual.onError(t); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDelaySubscription.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDelaySubscription.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDelaySubscription.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import org.reactivestreams.Publisher; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; + +/** + * Delays the subscription to the main source until another Publisher + * signals a value or completes. + * + * @param the main source value type + * @param the other source type + * @see Reactive-Streams-Commons + */ +final class MonoDelaySubscription extends InternalMonoOperator + implements Consumer> { + + final Publisher other; + + MonoDelaySubscription(Mono source, Publisher other) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + other.subscribe(new FluxDelaySubscription.DelaySubscriptionOtherSubscriber<>( + actual, this)); + return null; + } + + @Override + public void accept(FluxDelaySubscription.DelaySubscriptionOtherSubscriber s) { + source.subscribe(new FluxDelaySubscription.DelaySubscriptionMainSubscriber<>(s.actual, s)); + } + + @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/MonoDelayUntil.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDelayUntil.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDelayUntil.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,625 @@ +/* + * Copyright (c) 2017-2021 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.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Waits for a Mono source to terminate or produce a value, in which case the value is + * mapped to a Publisher used as a delay: its termination will trigger the actual emission + * of the value downstream. If the Mono source didn't produce a value, terminate with the + * same signal (empty completion or error). + * + * @param the value type + * + * @author Simon Baslé + */ +final class MonoDelayUntil extends Mono implements Scannable, + OptimizableOperator { + + final Mono source; + + Function>[] otherGenerators; + + @Nullable + final OptimizableOperator optimizableOperator; + + @SuppressWarnings("unchecked") + MonoDelayUntil(Mono monoSource, + Function> triggerGenerator) { + this.source = Objects.requireNonNull(monoSource, "monoSource"); + this.otherGenerators = new Function[] { Objects.requireNonNull(triggerGenerator, "triggerGenerator")}; + this.optimizableOperator = source instanceof OptimizableOperator ? (OptimizableOperator) source : null; + } + + MonoDelayUntil(Mono monoSource, + Function>[] triggerGenerators) { + this.source = Objects.requireNonNull(monoSource, "monoSource"); + this.otherGenerators = triggerGenerators; + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + /** + * Add a trigger generator to wait for. + * @param delayError the delayError parameter for the trigger being added, ignored if already true + * @param triggerGenerator the new trigger to add to the copy of the operator + * @return a new {@link MonoDelayUntil} instance with same source but additional trigger generator + */ + @SuppressWarnings("unchecked") + MonoDelayUntil copyWithNewTriggerGenerator(boolean delayError, + Function> triggerGenerator) { + Objects.requireNonNull(triggerGenerator, "triggerGenerator"); + Function>[] oldTriggers = this.otherGenerators; + Function>[] newTriggers = new Function[oldTriggers.length + 1]; + System.arraycopy(oldTriggers, 0, newTriggers, 0, oldTriggers.length); + newTriggers[oldTriggers.length] = triggerGenerator; + return new MonoDelayUntil<>(this.source, newTriggers); + } + + @Override + public void subscribe(CoreSubscriber actual) { + try { + source.subscribe(subscribeOrReturn(actual)); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + } + + @Override + public final CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable { + DelayUntilCoordinator parent = new DelayUntilCoordinator<>(actual, otherGenerators); + actual.onSubscribe(parent); + + return parent; + } + + @Override + public final CorePublisher source() { + return source; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; //no particular key to be represented, still useful in hooks + } + + static final class DelayUntilCoordinator implements InnerOperator { + + final Function>[] otherGenerators; + final CoreSubscriber actual; + + int index; + + T value; + boolean done; + + Subscription s; + DelayUntilTrigger triggerSubscriber; + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(DelayUntilCoordinator.class, Throwable.class, "error"); + + volatile int state; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(DelayUntilCoordinator.class, "state"); + + static final int HAS_SUBSCRIPTION = 0b00000000000000000000000000000001; + static final int HAS_INNER = 0b00000000000000000000000000000010; + static final int HAS_REQUEST = 0b00000000000000000000000000000100; + static final int HAS_VALUE = 0b00000000000000000000000000001000; + static final int TERMINATED = 0b10000000000000000000000000000000; + + DelayUntilCoordinator(CoreSubscriber subscriber, + Function>[] otherGenerators) { + this.actual = subscriber; + this.otherGenerators = otherGenerators; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + int previousState = markHasSubscription(); + if (isTerminated(previousState)) { + s.cancel(); + return; + } + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (this.done) { + Operators.onDiscard(t, this.actual.currentContext()); + return; + } + + this.value = t; + subscribeNextTrigger(); + } + + @Override + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, this.actual.currentContext()); + return; + } + + this.done = true; + + if (this.value == null) { + this.actual.onError(t); + return; + } + + if (!Exceptions.addThrowable(ERROR, this, t)) { + Operators.onErrorDropped(t, this.actual.currentContext()); + return; + } + + final int previousState = markTerminated(); + if (isTerminated(previousState)) { + return; + } + + if (hasInner(previousState)) { + Operators.onDiscard(this.value, this.actual.currentContext()); + this.triggerSubscriber.cancel(); + } + + final Throwable e = Exceptions.terminate(ERROR, this); + //noinspection ConstantConditions + this.actual.onError(e); + } + + @Override + public void onComplete() { + if (this.done) { + return; + } + + if (this.value == null) { + this.done = true; + this.actual.onComplete(); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + final int previousState = markHasRequest(); + + if (isTerminated(previousState)) { + return; + } + + if (hasRequest(previousState)) { + return; + } + + if (hasValue(previousState)) { + this.done = true; + final CoreSubscriber actual = this.actual; + final T v = this.value; + + actual.onNext(v); + actual.onComplete(); + } + } + } + + @Override + public void cancel() { + final int previousState = markTerminated(); + + if (isTerminated(previousState)) { + return; + } + + final Throwable t = Exceptions.terminate(ERROR, this); + if (t != null) { + Operators.onErrorDropped(t, this.actual.currentContext()); + } + + if (hasSubscription(previousState)) { + this.s.cancel(); + } + + if (hasInner(previousState)) { + Operators.onDiscard(this.value, this.actual.currentContext()); + + this.triggerSubscriber.cancel(); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + void subscribeNextTrigger() { + final Function> generator = + this.otherGenerators[this.index]; + + final Publisher p; + + try { + p = generator.apply(this.value); + Objects.requireNonNull(p, "mapper returned null value"); + } + catch (Throwable t) { + onError(t); + return; + } + + DelayUntilTrigger triggerSubscriber = this.triggerSubscriber; + if (triggerSubscriber == null) { + triggerSubscriber = new DelayUntilTrigger<>(this); + this.triggerSubscriber = triggerSubscriber; + } + + p.subscribe(triggerSubscriber); + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return isTerminated(this.state) && !this.done; + if (key == Attr.TERMINATED) return isTerminated(this.state) && this.done; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + final DelayUntilTrigger subscriber = this.triggerSubscriber; + return subscriber == null ? Stream.empty() : Stream.of(subscriber); + } + + /** + * Sets flag has subscription to indicate that we have already received + * subscription from the value upstream + * + * @return previous state + */ + int markHasSubscription() { + for (;;) { + final int state = this.state; + + if (isTerminated(state)) { + return TERMINATED; + } + + if (STATE.compareAndSet(this, state, state | HAS_SUBSCRIPTION)) { + return state; + } + } + } + + /** + * Sets {@link #HAS_REQUEST} flag which indicates that there is a demand from + * the downstream + * + * @return previous state + */ + int markHasRequest() { + for (; ; ) { + final int state = this.state; + if (isTerminated(state)) { + return TERMINATED; + } + + if (hasRequest(state)) { + return state; + } + + final int nextState; + if (hasValue(state)) { + nextState = TERMINATED; + } + else { + nextState = state | HAS_REQUEST; + } + + if (STATE.compareAndSet(this, state, nextState)) { + return state; + } + } + } + + /** + * Sets current state to {@link #TERMINATED} + * + * @return previous state + */ + int markTerminated() { + for (;;) { + final int state = this.state; + + if (isTerminated(state)) { + return TERMINATED; + } + + if (STATE.compareAndSet(this, state, TERMINATED)) { + return state; + } + } + } + + /** + * Terminates execution if there is a demand from the downstream or sets + * {@link #HAS_VALUE} flag indicating that the delay process is completed + * however there is no demand from the downstream yet + */ + void complete() { + for (; ; ) { + int s = this.state; + + if (isTerminated(s)) { + return; + } + + if (hasRequest(s) && STATE.compareAndSet(this, s, TERMINATED)) { + final CoreSubscriber actual = this.actual; + final T v = this.value; + + actual.onNext(v); + actual.onComplete(); + + return; + } + + if (STATE.compareAndSet(this, s, s | HAS_VALUE)) { + return; + } + } + } + } + + static final class DelayUntilTrigger implements InnerConsumer { + + final DelayUntilCoordinator parent; + + Subscription s; + boolean done; + Throwable error; + + DelayUntilTrigger(DelayUntilCoordinator parent) { + this.parent = parent; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return isTerminated(this.parent.state) && !this.done; + if (key == Attr.PARENT) return this.s; + if (key == Attr.ACTUAL) return this.parent; + if (key == Attr.ERROR) return this.error; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + int previousState = markInnerActive(); + if (isTerminated(previousState)) { + s.cancel(); + + final DelayUntilCoordinator parent = this.parent; + Operators.onDiscard(parent.value, parent.currentContext()); + + return; + } + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + //NO-OP + Operators.onDiscard(t, this.parent.currentContext()); + } + + @Override + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, parent.currentContext()); + return; + } + + final DelayUntilCoordinator parent = this.parent; + + this.done = true; + parent.done = true; + + if (!Exceptions.addThrowable(DelayUntilCoordinator.ERROR, parent, t)) { + Operators.onErrorDropped(t, parent.currentContext()); + return; + } + + final int previousState = parent.markTerminated(); + if (isTerminated(previousState)) { + return; + } + + Operators.onDiscard(parent.value, parent.currentContext()); + + parent.s.cancel(); + + final Throwable e = Exceptions.terminate(DelayUntilCoordinator.ERROR, parent); + //noinspection ConstantConditions + parent.actual.onError(e); + } + + @Override + public void onComplete() { + if (this.done) { + return; + } + + this.done = true; + + final DelayUntilCoordinator parent = this.parent; + final int nextIndex = parent.index + 1; + + parent.index = nextIndex; + + if (nextIndex == parent.otherGenerators.length) { + parent.complete(); + return; + } + + final int previousState = markInnerInactive(); + if (isTerminated(previousState)) { + return; + } + + // we have to reset the state since we reuse the same object for the + // optimization purpose and at this stage we know that there is another + // delayer Publisher to subscribe to + this.done = false; + this.s = null; + + parent.subscribeNextTrigger(); + } + + void cancel() { + this.s.cancel(); + } + + /** + * Sets flag {@link DelayUntilCoordinator#HAS_INNER} which indicates that there + * is an active delayer Publisher + * + * @return previous state + */ + int markInnerActive() { + final DelayUntilCoordinator parent = this.parent; + for (;;) { + final int state = parent.state; + + if (isTerminated(state)) { + return DelayUntilCoordinator.TERMINATED; + } + + if (hasInner(state)) { + return state; + } + + if (DelayUntilCoordinator.STATE.compareAndSet(parent, state, state | DelayUntilCoordinator.HAS_INNER)) { + return state; + } + } + } + + /** + * Unsets flag {@link DelayUntilCoordinator#HAS_INNER} + * + * @return previous state + */ + int markInnerInactive() { + final DelayUntilCoordinator parent = this.parent; + for (;;) { + final int state = parent.state; + + if (isTerminated(state)) { + return DelayUntilCoordinator.TERMINATED; + } + + if (!hasInner(state)) { + return state; + } + + if (DelayUntilCoordinator.STATE.compareAndSet(parent, state, state &~ DelayUntilCoordinator.HAS_INNER)) { + return state; + } + } + } + } + + /** + * Indicates if state is ended with cancellation | error | complete + */ + static boolean isTerminated(int state) { + return state == DelayUntilCoordinator.TERMINATED; + } + + static boolean hasValue(int state) { + return (state & DelayUntilCoordinator.HAS_VALUE) == DelayUntilCoordinator.HAS_VALUE; + } + + static boolean hasInner(int state) { + return (state & DelayUntilCoordinator.HAS_INNER) == DelayUntilCoordinator.HAS_INNER; + } + + static boolean hasRequest(int state) { + return (state & DelayUntilCoordinator.HAS_REQUEST) == DelayUntilCoordinator.HAS_REQUEST; + } + + static boolean hasSubscription(int state) { + return (state & DelayUntilCoordinator.HAS_SUBSCRIPTION) == DelayUntilCoordinator.HAS_SUBSCRIPTION; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoDematerialize.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDematerialize.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDematerialize.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * @author Stephane Maldini + */ +final class MonoDematerialize extends InternalMonoOperator, T> { + + MonoDematerialize(Mono> source) { + super(source); + } + + @Override + public CoreSubscriber> subscribeOrReturn(CoreSubscriber actual) { + return new FluxDematerialize.DematerializeSubscriber<>(actual, true); + } + + @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/MonoDetach.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDetach.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDetach.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Detaches both the child Subscriber and the Subscription on + * termination or cancellation. + *

This should help with odd retention scenarios when running + * wit non Rx mentality based Publishers. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoDetach extends InternalMonoOperator { + + MonoDetach(Mono source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxDetach.DetachSubscriber<>(actual); + } + + @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/MonoDoFinally.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFinally.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFinally.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; + +/** + * Hook into the lifecycle events and signals of a {@link Mono} and execute + * a provided callback after any of onComplete, onError and cancel events. + * The hook is executed only once and receives the event type that triggered + * it ({@link SignalType#ON_COMPLETE}, {@link SignalType#ON_ERROR} or + * {@link SignalType#CANCEL}). + *

+ * Note that any exception thrown by the hook are caught and bubbled up + * using {@link Operators#onErrorDropped(Throwable, reactor.util.context.Context)}. + * + * @param the value type + * @author Simon Baslé + */ +final class MonoDoFinally extends InternalMonoOperator { + + final Consumer onFinally; + + MonoDoFinally(Mono source, Consumer onFinally) { + super(source); + this.onFinally = onFinally; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return FluxDoFinally.createSubscriber(actual, onFinally, 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/MonoDoFinallyFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFinallyFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFinallyFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Hook with fusion into the lifecycle events and signals of a {@link Mono} + * and execute a provided callback after any of onComplete, onError and cancel events. + * The hook is executed only once and receives the event type that triggered + * it ({@link SignalType#ON_COMPLETE}, {@link SignalType#ON_ERROR} or + * {@link SignalType#CANCEL}). + *

+ * Note that any exception thrown by the hook are caught and bubbled up + * using {@link Operators#onErrorDropped(Throwable, reactor.util.context.Context)}. + * + * @param the value type + * @author Simon Baslé + */ +final class MonoDoFinallyFuseable extends InternalMonoOperator implements Fuseable { + + final Consumer onFinally; + + MonoDoFinallyFuseable(Mono source, Consumer onFinally) { + super(source); + this.onFinally = onFinally; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return FluxDoFinally.createSubscriber(actual, onFinally, true); + } + + @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/MonoDoFirst.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFirst.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFirst.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016-2021 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.Subscriber; + +import reactor.core.CoreSubscriber; + +/** + * Hook into the {@link org.reactivestreams.Publisher#subscribe(Subscriber)} of a + * {@link Mono} and execute a provided callback before calling + * {@link org.reactivestreams.Publisher#subscribe(Subscriber)} directly with the + * {@link CoreSubscriber}. + * + *

+ * Note that any exception thrown by the hook short circuit the subscription process and + * are forwarded to the {@link Subscriber}'s {@link Subscriber#onError(Throwable)} method. + * + * @param the value type + * @author Simon Baslé + */ +final class MonoDoFirst extends InternalMonoOperator { + + final Runnable onFirst; + + MonoDoFirst(Mono source, Runnable onFirst) { + super(source); + this.onFirst = onFirst; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + onFirst.run(); + + return actual; + } + + @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/MonoDoFirstFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFirstFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDoFirstFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016-2021 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.Subscriber; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Hook into the {@link org.reactivestreams.Publisher#subscribe(Subscriber)} of a + * {@link Mono} and execute a provided callback before calling + * {@link org.reactivestreams.Publisher#subscribe(Subscriber)} directly with the + * {@link CoreSubscriber}. + * + *

+ * Note that any exception thrown by the hook short circuit the subscription process and + * are forwarded to the {@link Subscriber}'s {@link Subscriber#onError(Throwable)} method. + * + * @param the value type + * @author Simon Baslé + */ +final class MonoDoFirstFuseable extends InternalMonoOperator implements Fuseable { + + final Runnable onFirst; + + MonoDoFirstFuseable(Mono source, Runnable onFirst) { + super(source); + this.onFirst = onFirst; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + onFirst.run(); + + return actual; + } + + @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/MonoDoOnEach.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDoOnEach.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDoOnEach.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; + +/** + * Peek into the lifecycle events and signals of a sequence + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class MonoDoOnEach extends InternalMonoOperator { + + final Consumer> onSignal; + + MonoDoOnEach(Mono source, Consumer> onSignal) { + super(source); + this.onSignal = Objects.requireNonNull(onSignal, "onSignal"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return FluxDoOnEach.createSubscriber(actual, onSignal, false, true); + } + + @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/MonoDoOnEachFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoDoOnEachFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoDoOnEachFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Peek into the lifecycle events and signals of a sequence, {@link reactor.core.Fuseable} + * version of {@link MonoDoOnEach}. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class MonoDoOnEachFuseable extends InternalMonoOperator implements Fuseable { + + final Consumer> onSignal; + + MonoDoOnEachFuseable(Mono source, Consumer> onSignal) { + super(source); + this.onSignal = Objects.requireNonNull(onSignal, "onSignal"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return FluxDoOnEach.createSubscriber(actual, onSignal, true, true); + } + + @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/MonoElapsed.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoElapsed.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoElapsed.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016-2021 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.scheduler.Scheduler; +import reactor.util.function.Tuple2; + +/** + * @author Stephane Maldini + */ +final class MonoElapsed extends InternalMonoOperator> implements Fuseable { + + final Scheduler scheduler; + + MonoElapsed(Mono source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new FluxElapsed.ElapsedSubscriber(actual, scheduler); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoElementAt.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoElementAt.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoElementAt.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Emits only the element at the given index position or signals a + * default value if specified or IndexOutOfBoundsException if the sequence is shorter. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoElementAt extends MonoFromFluxOperator + implements Fuseable { + + final long index; + + final T defaultValue; + + MonoElementAt(Flux source, long index) { + super(source); + if (index < 0) { + throw new IndexOutOfBoundsException("index >= required but it was " + index); + } + this.index = index; + this.defaultValue = null; + } + + MonoElementAt(Flux source, long index, T defaultValue) { + super(source); + if (index < 0) { + throw new IndexOutOfBoundsException("index >= required but it was " + index); + } + this.index = index; + this.defaultValue = Objects.requireNonNull(defaultValue, "defaultValue"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new ElementAtSubscriber<>(actual, index, defaultValue); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ElementAtSubscriber + extends Operators.MonoSubscriber { + final T defaultValue; + + long index; + + final long target; + + Subscription s; + + boolean done; + + ElementAtSubscriber(CoreSubscriber actual, long index, + T defaultValue) { + super(actual); + this.index = index; + this.target = index; + this.defaultValue = defaultValue; + } + + @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 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()); + return; + } + + long i = index; + if (i == 0) { + done = true; + s.cancel(); + + actual.onNext(t); + actual.onComplete(); + return; + } + index = i - 1; + Operators.onDiscard(t, actual.currentContext()); //FIXME cache currentcontext + } + + @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; + + if(defaultValue != null) { + complete(defaultValue); + } + else{ + long count = target - index; + actual.onError(Operators.onOperatorError(new IndexOutOfBoundsException( + "source had " + count + " elements, expected at least " + (target + 1)), + actual.currentContext())); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoEmpty.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoEmpty.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoEmpty.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Represents an empty publisher which only calls onSubscribe and onComplete. + *

+ * This Publisher is effectively stateless and only a single instance exists. + * Use the {@link #instance()} method to obtain a properly type-parametrized view of it. + * + * @see Reactive-Streams-Commons + */ +final class MonoEmpty +extends Mono + implements Fuseable.ScalarCallable, SourceProducer { + + static final Publisher INSTANCE = new MonoEmpty(); + + MonoEmpty() { + // deliberately no op + } + + @Override + public void subscribe(CoreSubscriber actual) { + Operators.complete(actual); + } + + /** + * Returns a properly parametrized instance of this empty Publisher. + * + * @param the output type + * @return a properly parametrized instance of this empty Publisher + */ + @SuppressWarnings("unchecked") + static Mono instance() { + return (Mono) INSTANCE; + } + + @Override + @Nullable + public Object call() throws Exception { + return null; /* Scalar optimizations on empty */ + } + + @Override + @Nullable + public Object block(Duration m) { + return null; + } + + @Override + @Nullable + public Object block() { + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoError.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoError.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoError.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; + + +/** + * Emits a constant or generated Throwable instance to Subscribers. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoError extends Mono implements Fuseable.ScalarCallable, SourceProducer { + + final Throwable error; + + MonoError(Throwable error) { + this.error = Objects.requireNonNull(error, "error"); + } + + @Override + public T block(Duration m) { + throw Exceptions.propagate(error); + } + + @Override + public T block() { + throw Exceptions.propagate(error); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Operators.error(actual, error); + } + + @Override + public Object call() throws Exception { + if(error instanceof Exception){ + throw ((Exception)error); + } + throw Exceptions.propagate(error); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoErrorSupplied.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoErrorSupplied.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoErrorSupplied.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016-2021 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.function.Supplier; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; + +/** + * Emits a lazily generated {@link Throwable} instance to Subscribers, via a {@link Supplier}. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoErrorSupplied extends Mono implements Fuseable.ScalarCallable, SourceProducer { + + final Supplier errorSupplier; + + MonoErrorSupplied(Supplier errorSupplier) { + this.errorSupplier = Objects.requireNonNull(errorSupplier, "errorSupplier"); + } + + @Override + public T block(Duration m) { + Throwable error = Objects.requireNonNull(errorSupplier.get(), "the errorSupplier returned null"); + throw Exceptions.propagate(error); + } + + @Override + public T block() { + Throwable error = Objects.requireNonNull(errorSupplier.get(), "the errorSupplier returned null"); + throw Exceptions.propagate(error); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Throwable error = Objects.requireNonNull(errorSupplier.get(), "the errorSupplier returned null"); + Operators.error(actual, error); + } + + @Override + public T call() throws Exception { + Throwable error = Objects.requireNonNull(errorSupplier.get(), "the errorSupplier returned null"); + if(error instanceof Exception){ + throw ((Exception) error); + } + throw Exceptions.propagate(error); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoExpand.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoExpand.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoExpand.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017-2021 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.Function; + +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; + +/** + * A {@link Flux} that emits the item from a {@link Mono} and recursively expand it into + * inner sequences that are also replayed. The type of recursion is driven by the + * {@code breadthFirst} parameter. + * + * @author David Karnok + * @author Simon Baslé + */ +final class MonoExpand extends FluxFromMonoOperator { + + final boolean breadthFirst; + final Function> expander; + final int capacityHint; + + MonoExpand(Mono source, + Function> expander, + boolean breadthFirst, int capacityHint) { + super(source); + this.expander = expander; + this.breadthFirst = breadthFirst; + this.capacityHint = capacityHint; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber s) { + if (breadthFirst) { + FluxExpand.ExpandBreathSubscriber parent = + new FluxExpand.ExpandBreathSubscriber<>(s, expander, capacityHint); + parent.queue.offer(source); + s.onSubscribe(parent); + parent.drainQueue(); + } + else { + FluxExpand.ExpandDepthSubscription parent = + new FluxExpand.ExpandDepthSubscription<>(s, expander, capacityHint); + parent.source = source; + s.onSubscribe(parent); + } + return null; + } + + @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/MonoExtensions.kt =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoExtensions.kt (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoExtensions.kt (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2011-2021 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.Publisher +import java.util.concurrent.Callable +import java.util.concurrent.CompletableFuture +import java.util.function.Supplier +import kotlin.reflect.KClass + +/** + * Extension to convert any [Publisher] of [T] to a [Mono] that only emits its first + * element. + * + * Note this extension doesn't make much sense on a [Mono] but it won't be converted so it + * doesn't hurt. + * + * @author Simon Baslé + * @since 3.1.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toMono()", "reactor.kotlin.core.publisher.toMono")) +fun Publisher.toMono(): Mono = Mono.from(this) + +/** + * Extension to convert any [Supplier] of [T] to a [Mono] that emits supplied element. + * + * @author Sergio Dos Santos + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toMono()", "reactor.kotlin.core.publisher.toMono")) +fun (() -> T?).toMono(): Mono = Mono.fromSupplier(this) + +/** + * Extension for transforming an object to a [Mono]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toMono()", "reactor.kotlin.core.publisher.toMono")) +fun T.toMono(): Mono = Mono.just(this) + +/** + * Extension for transforming an [CompletableFuture] to a [Mono]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toMono()", "reactor.kotlin.core.publisher.toMono")) +fun CompletableFuture.toMono(): Mono = Mono.fromFuture(this) + +/** + * Extension for transforming an [Callable] to a [Mono]. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toMono()", "reactor.kotlin.core.publisher.toMono")) +fun Callable.toMono(): Mono = Mono.fromCallable(this::call) + +/** + * Extension for transforming an exception to a [Mono] that completes with the specified error. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("toMono()", "reactor.kotlin.core.publisher.toMono")) +fun Throwable.toMono(): Mono = Mono.error(this) + +/** + * Extension for [Mono.cast] providing a `cast()` variant. + * + * @author Sebastien + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("cast()", "reactor.kotlin.core.publisher.cast")) +inline fun Mono<*>.cast(): Mono = cast(T::class.java) + +/** + * Extension for [Mono.doOnError] providing a [KClass] based variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("doOnError(exceptionType, onError)", "reactor.kotlin.core.publisher.doOnError")) +fun Mono.doOnError(exceptionType: KClass, onError: (E) -> Unit): Mono = + doOnError(exceptionType.java) { onError(it) } + +/** + * Extension for [Mono.onErrorMap] providing a [KClass] based variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("onErrorMap(exceptionType, mapper)", "reactor.kotlin.core.publisher.onErrorMap")) +fun Mono.onErrorMap(exceptionType: KClass, mapper: (E) -> Throwable): Mono = + onErrorMap(exceptionType.java) { mapper(it) } + +/** + * Extension for [Mono.ofType] providing a `ofType()` variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("ofType()", "reactor.kotlin.core.publisher.ofType")) +inline fun Mono<*>.ofType(): Mono = ofType(T::class.java) + +/** + * Extension for [Mono.onErrorResume] providing a [KClass] based variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("onErrorResume(exceptionType, fallback)", "reactor.kotlin.core.publisher.onErrorResume")) +fun Mono.onErrorResume(exceptionType: KClass, fallback: (E) -> Mono): Mono = + onErrorResume(exceptionType.java) { fallback(it) } + +/** + * Extension for [Mono.onErrorReturn] providing a [KClass] based variant. + * + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("onErrorReturn(exceptionType, value)", "reactor.kotlin.core.publisher.onErrorReturn")) +fun Mono.onErrorReturn(exceptionType: KClass, value: T): Mono = + onErrorReturn(exceptionType.java, value) + +/** + * Extension for [Mono.switchIfEmpty] accepting a function providing a Mono. This allows having a deferred execution with + * the [switchIfEmpty] operator + * + * @author Kevin Davin + * @since 3.2 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("switchIfEmpty(s)", "reactor.kotlin.core.publisher.switchIfEmpty")) +fun Mono.switchIfEmpty(s: () -> Mono): Mono = this.switchIfEmpty(Mono.defer { s() }) Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFilter.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFilter.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFilter.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable.ConditionalSubscriber; + +/** + * Filters out values that make a filter function return false. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoFilter extends InternalMonoOperator { + + final Predicate predicate; + + MonoFilter(Mono source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new FluxFilter.FilterConditionalSubscriber<>((ConditionalSubscriber) actual, predicate); + } + return new FluxFilter.FilterSubscriber<>(actual, predicate); + } + + @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/MonoFilterFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFilterFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFilterFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016-2021 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.Predicate; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Filters out values that make a filter function return false. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoFilterFuseable extends InternalMonoOperator + implements Fuseable { + + final Predicate predicate; + + MonoFilterFuseable(Mono source, Predicate predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new FluxFilterFuseable.FilterFuseableConditionalSubscriber<>((ConditionalSubscriber) actual, predicate); + } + return new FluxFilterFuseable.FilterFuseableSubscriber<>(actual, predicate); + } + + @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/MonoFilterWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFilterWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFilterWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2017-2021 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.Callable; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +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.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; + +/** + * Maps the upstream value into a single {@code true} or {@code false} value + * provided by a generated Publisher for that input value and emits the input value if + * the inner Publisher returned {@code true}. + *

+ * Only the first item emitted by the inner Publisher's are considered. If + * the inner Publisher is empty, no resulting item is generated for that input value. + * + * @param the input value type + * @author Simon Baslé + */ +class MonoFilterWhen extends InternalMonoOperator { + + final Function> asyncPredicate; + + MonoFilterWhen(Mono source, + Function> asyncPredicate) { + super(source); + this.asyncPredicate = asyncPredicate; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new MonoFilterWhenMain<>(actual, asyncPredicate); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == RUN_STYLE) return SYNC; + return super.scanUnsafe(key); + } + + static final class MonoFilterWhenMain extends Operators.MonoSubscriber { + + /* 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; + + //this is only touched by onNext and read by onComplete, so no need for volatile + boolean sourceValued; + + Subscription upstream; + + volatile FilterWhenInner asyncFilter; + + static final AtomicReferenceFieldUpdater ASYNC_FILTER = + AtomicReferenceFieldUpdater.newUpdater(MonoFilterWhenMain.class, FilterWhenInner.class, "asyncFilter"); + + @SuppressWarnings("ConstantConditions") + static final FilterWhenInner INNER_CANCELLED = new FilterWhenInner(null, false); + + MonoFilterWhenMain(CoreSubscriber actual, Function> asyncPredicate) { + super(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); + } + } + + @SuppressWarnings("unchecked") + @Override + public void onNext(T t) { + //we assume the source is a Mono, so only one onNext will ever happen + sourceValued = true; + setValue(t); + Publisher p; + + try { + p = Objects.requireNonNull(asyncPredicate.apply(t), + "The asyncPredicate returned a null value"); + } + catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + super.onError(ex); + Operators.onDiscard(t, actual.currentContext()); + return; + } + + if (p instanceof Callable) { + Boolean u; + + try { + u = ((Callable) p).call(); + } + catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + super.onError(ex); + Operators.onDiscard(t, actual.currentContext()); + return; + } + + if (u != null && u) { + complete(t); + } + else { + actual.onComplete(); + Operators.onDiscard(t, actual.currentContext()); + } + } + else { + FilterWhenInner inner = new FilterWhenInner(this, !(p instanceof Mono)); + if (ASYNC_FILTER.compareAndSet(this, null, inner)) { + p.subscribe(inner); + } + } + } + + @Override + public void onComplete() { + if (!sourceValued) { + //there was no value, we can complete empty + super.onComplete(); + } + //otherwise just wait for the inner filter to apply, rather than complete too soon + } + + /* 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(); + } + } + + void cancelInner() { + FilterWhenInner a = asyncFilter; + if (a != INNER_CANCELLED) { + a = ASYNC_FILTER.getAndSet(this, INNER_CANCELLED); + if (a != null && a != INNER_CANCELLED) { + 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); + } + else { + super.onComplete(); + discard(this.value); + } + } + + void innerError(Throwable ex) { + //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); + } + + @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; + + //CANCELLED, PREFETCH + return super.scanUnsafe(key); + } + + @Override + public Stream inners() { + FilterWhenInner c = asyncFilter; + return c == null ? Stream.empty() : Stream.of(c); + } + } + + static final class FilterWhenInner implements InnerConsumer { + + final MonoFilterWhenMain main; + /** should the filter publisher be cancelled once we received the first value? */ + final boolean cancelOnNext; + + boolean done; + + volatile Subscription sub; + + static final AtomicReferenceFieldUpdater SUB = + AtomicReferenceFieldUpdater.newUpdater(FilterWhenInner.class, Subscription.class, "sub"); + + FilterWhenInner(MonoFilterWhenMain main, boolean cancelOnNext) { + this.main = main; + this.cancelOnNext = cancelOnNext; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(SUB, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Boolean t) { + if (!done) { + if (cancelOnNext) { + sub.cancel(); + } + done = true; + main.innerResult(t); + } + } + + @Override + public void onError(Throwable t) { + if (!done) { + done = true; + main.innerError(t); + } else { + Operators.onErrorDropped(t, main.currentContext()); + } + } + + @Override + public Context currentContext() { + return main.currentContext(); + } + + @Override + public void onComplete() { + if (!done) { + //the filter publisher was empty + done = true; + main.innerResult(null); //will trigger actual.onComplete() + } + } + + void cancel() { + Operators.terminate(SUB, this); + } + + @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.TERMINATED) return done; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return done ? 0L : 1L; + if (key == RUN_STYLE) return SYNC; + + return null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFirstWithSignal.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFirstWithSignal.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFirstWithSignal.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.Objects; + +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Given a set of source Publishers the values of that Publisher is forwarded to the + * actual which responds first with any signal. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class MonoFirstWithSignal extends Mono implements SourceProducer { + + final Mono[] array; + + final Iterable> iterable; + + @SafeVarargs + MonoFirstWithSignal(Mono... array) { + this.array = Objects.requireNonNull(array, "array"); + this.iterable = null; + } + + MonoFirstWithSignal(Iterable> iterable) { + this.array = null; + this.iterable = Objects.requireNonNull(iterable); + } + + @Nullable + Mono orAdditionalSource(Mono other) { + if (array != null) { + int n = array.length; + @SuppressWarnings("unchecked") Mono[] newArray = new Mono[n + 1]; + System.arraycopy(array, 0, newArray, 0, n); + newArray[n] = other; + + return new MonoFirstWithSignal<>(newArray); + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public void subscribe(CoreSubscriber actual) { + Publisher[] a = array; + int n; + if (a == null) { + n = 0; + a = new Publisher[8]; + + Iterator> it; + + try { + it = Objects.requireNonNull(iterable.iterator(), "The iterator returned is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + for (; ; ) { + + boolean b; + + try { + b = it.hasNext(); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (!b) { + break; + } + + Publisher p; + + try { + p = Objects.requireNonNull(it.next(), + "The Publisher returned by the iterator is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (n == a.length) { + Publisher[] c = new Publisher[n + (n >> 2)]; + System.arraycopy(a, 0, c, 0, n); + a = c; + } + a[n++] = p; + } + + } + else { + n = a.length; + } + + if (n == 0) { + Operators.complete(actual); + return; + } + if (n == 1) { + Publisher p = a[0]; + + if (p == null) { + Operators.error(actual, + Operators.onOperatorError(new NullPointerException("The single source Publisher is null"), + actual.currentContext())); + } + else { + p.subscribe(actual); + } + return; + } + + FluxFirstWithSignal.RaceCoordinator coordinator = + new FluxFirstWithSignal.RaceCoordinator<>(n); + + coordinator.subscribe(a, n, actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFirstWithValue.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFirstWithValue.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFirstWithValue.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2016-2021 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.Publisher; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +import java.util.Iterator; +import java.util.Objects; + +final class MonoFirstWithValue extends Mono implements SourceProducer { + + final Mono[] array; + + final Iterable> iterable; + + private MonoFirstWithValue(Mono[] array) { + this.array = Objects.requireNonNull(array, "array"); + this.iterable = null; + } + + @SafeVarargs + MonoFirstWithValue(Mono first, Mono... others) { + Objects.requireNonNull(first, "first"); + Objects.requireNonNull(others, "others"); + @SuppressWarnings("unchecked") Mono[] newArray = new Mono[others.length + 1]; + newArray[0] = first; + System.arraycopy(others, 0, newArray, 1, others.length); + this.array = newArray; + this.iterable = null; + } + + MonoFirstWithValue(Iterable> iterable) { + this.array = null; + this.iterable = Objects.requireNonNull(iterable); + } + + /** + * Returns a new instance which has the additional sources to be flattened together with + * the current array of sources. + *

+ * This operation doesn't change the current {@link MonoFirstWithValue} instance. + * + * @param others the new sources to merge with the current sources + * + * @return the new {@link MonoFirstWithValue} instance or null if new sources cannot be added (backed by an Iterable) + */ + @Nullable + @SafeVarargs + final MonoFirstWithValue firstValuedAdditionalSources(Mono... others) { + Objects.requireNonNull(others, "others"); + if (others.length == 0) { + return this; + } + if (array == null) { + //iterable mode, returning null to convey 2 nested operators are needed here + return null; + } + int currentSize = array.length; + int otherSize = others.length; + @SuppressWarnings("unchecked") Mono[] newArray = new Mono[currentSize + otherSize]; + System.arraycopy(array, 0, newArray, 0, currentSize); + System.arraycopy(others, 0, newArray, currentSize, otherSize); + + return new MonoFirstWithValue<>(newArray); + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + Publisher[] a = array; + int n; + if (a == null) { + n = 0; + a = new Publisher[8]; + + Iterator> it; + + try { + it = Objects.requireNonNull(iterable.iterator(), "The iterator returned is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + for (; ; ) { + + boolean b; + + try { + b = it.hasNext(); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (!b) { + break; + } + + Publisher p; + + try { + p = Objects.requireNonNull(it.next(), + "The Publisher returned by the iterator is null"); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, + actual.currentContext())); + return; + } + + if (n == a.length) { + Publisher[] c = new Publisher[n + (n >> 2)]; + System.arraycopy(a, 0, c, 0, n); + a = c; + } + a[n++] = p; + } + + } + else { + n = a.length; + } + + if (n == 0) { + Operators.complete(actual); + return; + } + if (n == 1) { + Publisher p = a[0]; + + if (p == null) { + Operators.error(actual, + Operators.onOperatorError(new NullPointerException("The single source Publisher is null"), + actual.currentContext())); + } + else { + p.subscribe(actual); + } + return; + } + + FluxFirstWithValue.RaceValuesCoordinator coordinator = + new FluxFirstWithValue.RaceValuesCoordinator<>(n); + + coordinator.subscribe(a, n, actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFlatMap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFlatMap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFlatMap.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Given a Mono source, applies a function on its single item and continues + * with that Mono instance, emitting its final result. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class MonoFlatMap extends InternalMonoOperator implements Fuseable { + + final Function> mapper; + + MonoFlatMap(Mono source, + Function> mapper) { + super(source); + this.mapper = Objects.requireNonNull(mapper, "mapper"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + //for now Mono in general doesn't support onErrorContinue, so the scalar version shouldn't either + if (FluxFlatMap.trySubscribeScalarMap(source, actual, mapper, true, false)) { + return null; + } + + FlatMapMain manager = new FlatMapMain<>(actual, mapper); + actual.onSubscribe(manager); + + return manager; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class FlatMapMain extends Operators.MonoSubscriber { + + final Function> mapper; + + final FlatMapInner second; + + boolean done; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(FlatMapMain.class, + Subscription.class, + "s"); + + FlatMapMain(CoreSubscriber subscriber, + Function> mapper) { + super(subscriber); + this.mapper = mapper; + this.second = new FlatMapInner<>(this); + } + + @Override + public Stream inners() { + return Stream.of(second); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + 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.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + done = true; + + Mono m; + + try { + m = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null Mono"); + } + catch (Throwable ex) { + actual.onError(Operators.onOperatorError(s, ex, t, + actual.currentContext())); + return; + } + + if (m instanceof Callable) { + @SuppressWarnings("unchecked") Callable c = (Callable) m; + + R v; + try { + v = c.call(); + } + catch (Throwable ex) { + actual.onError(Operators.onOperatorError(s, ex, t, + actual.currentContext())); + return; + } + + if (v == null) { + actual.onComplete(); + } + else { + complete(v); + } + return; + } + + try { + m.subscribe(second); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(this, e, t, + actual.currentContext())); + } + } + + @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; + actual.onComplete(); + } + + @Override + public void cancel() { + super.cancel(); + Operators.terminate(S, this); + second.cancel(); + } + + void secondError(Throwable ex) { + actual.onError(ex); + } + + void secondComplete() { + actual.onComplete(); + } + } + + static final class FlatMapInner implements InnerConsumer { + + final FlatMapMain parent; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(FlatMapInner.class, + Subscription.class, + "s"); + + boolean done; + + FlatMapInner(FlatMapMain parent) { + this.parent = parent; + } + + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(R t) { + if (done) { + Operators.onNextDropped(t, parent.currentContext()); + return; + } + done = true; + this.parent.complete(t); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, parent.currentContext()); + return; + } + done = true; + this.parent.secondError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + this.parent.secondComplete(); + } + + void cancel() { + Operators.terminate(S, this); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFlatMapMany.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFlatMapMany.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFlatMapMany.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +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.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +final class MonoFlatMapMany extends FluxFromMonoOperator { + + + final Function> mapper; + + MonoFlatMapMany(Mono source, + Function> mapper) { + super(source); + this.mapper = mapper; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + //for now Mono in general doesn't support onErrorContinue, so the scalar version shouldn't either + //even if the result is a Flux. once the mapper is applied, onErrorContinue will be taken care of by + //the mapped Flux if relevant. + if (FluxFlatMap.trySubscribeScalarMap(source, actual, mapper, false, false)) { + return null; + } + return new FlatMapManyMain(actual, mapper); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class FlatMapManyMain implements InnerOperator { + + final CoreSubscriber actual; + + final Function> mapper; + + Subscription main; + + volatile Subscription inner; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater INNER = + AtomicReferenceFieldUpdater.newUpdater(FlatMapManyMain.class, + Subscription.class, + "inner"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(FlatMapManyMain.class, "requested"); + + boolean hasValue; + + FlatMapManyMain(CoreSubscriber actual, + Function> mapper) { + this.actual = actual; + this.mapper = mapper; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return main; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(Scannable.from(inner)); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + Subscription a = inner; + if (a != null) { + a.request(n); + } + else { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + a = inner; + if (a != null) { + n = REQUESTED.getAndSet(this, 0L); + if (n != 0L) { + a.request(n); + } + } + } + } + } + + @Override + public void cancel() { + main.cancel(); + Operators.terminate(INNER, this); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.main, s)) { + this.main = s; + + actual.onSubscribe(this); + + s.request(Long.MAX_VALUE); + } + } + + void onSubscribeInner(Subscription s) { + if (Operators.setOnce(INNER, this, s)) { + + long r = REQUESTED.getAndSet(this, 0L); + if (r != 0) { + s.request(r); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void onNext(T t) { + hasValue = true; + + Publisher p; + + try { + p = Objects.requireNonNull(mapper.apply(t), + "The mapper returned a null Publisher."); + } + catch (Throwable ex) { + //if the mapping fails, then there is nothing to be continued, since the source is a Mono + actual.onError(Operators.onOperatorError(this, ex, t, + actual.currentContext())); + return; + } + + if (p instanceof Callable) { + R v; + + try { + v = ((Callable) p).call(); + } + catch (Throwable ex) { + actual.onError(Operators.onOperatorError(this, ex, t, + actual.currentContext())); + return; + } + + if (v == null) { + actual.onComplete(); + } + else { + onSubscribeInner(Operators.scalarSubscription(actual, v)); + } + + return; + } + + p.subscribe(new FlatMapManyInner<>(this, actual)); + } + + @Override + public void onError(Throwable t) { + if (hasValue) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + actual.onError(t); + } + + @Override + public void onComplete() { + if (!hasValue) { + actual.onComplete(); + } + } + } + + static final class FlatMapManyInner implements InnerConsumer { + + final FlatMapManyMain parent; + + final CoreSubscriber actual; + + FlatMapManyInner(FlatMapManyMain parent, + CoreSubscriber actual) { + this.parent = parent; + this.actual = actual; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent.inner; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return parent.requested; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + parent.onSubscribeInner(s); + } + + @Override + public void onNext(R t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFlattenIterable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFlattenIterable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFlattenIterable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016-2021 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.Iterator; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.function.Supplier; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Concatenates values from Iterable sequences generated via a mapper function. + * + * @param the input value type + * @param the value type of the iterables and the result type + * + * @see Reactive-Streams-Commons + */ +final class MonoFlattenIterable extends FluxFromMonoOperator + implements Fuseable { + + final Function> mapper; + + final int prefetch; + + final Supplier> queueSupplier; + + MonoFlattenIterable(Mono source, + Function> mapper, + int prefetch, + Supplier> queueSupplier) { + super(source); + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.mapper = Objects.requireNonNull(mapper, "mapper"); + this.prefetch = prefetch; + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @SuppressWarnings("unchecked") + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Exception { + if (source instanceof Callable) { + T v = ((Callable) source).call(); + + if (v == null) { + Operators.complete(actual); + return null; + } + + Iterable iter = mapper.apply(v); + Iterator it = iter.iterator(); + boolean itFinite = FluxIterable.checkFinite(iter); + + FluxIterable.subscribe(actual, it, itFinite); + + return null; + } + return new FluxFlattenIterable.FlattenIterableSubscriber<>(actual, + mapper, + prefetch, + queueSupplier); + } + + @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/MonoFromFluxOperator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFromFluxOperator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFromFluxOperator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * 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}. + * + * @param delegate {@link Publisher} type + * @param produced type + */ +abstract class MonoFromFluxOperator extends Mono implements Scannable, + OptimizableOperator { + + protected final Flux source; + + @Nullable + final OptimizableOperator optimizableOperator; + + /** + * Build a {@link MonoFromFluxOperator} wrapper around the passed parent {@link Publisher} + * + * @param source the {@link Publisher} to decorate + */ + protected MonoFromFluxOperator(Flux source) { + this.source = Objects.requireNonNull(source); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator sourceOptim = (OptimizableOperator) source; + this.optimizableOperator = sourceOptim; + } + else { + this.optimizableOperator = null; + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.PARENT) return source; + return null; + } + + @Override + @SuppressWarnings("unchecked") + public final void subscribe(CoreSubscriber subscriber) { + OptimizableOperator operator = this; + try { + while (true) { + subscriber = operator.subscribeOrReturn(subscriber); + if (subscriber == null) { + // null means "I will subscribe myself", returning... + return; + } + OptimizableOperator newSource = operator.nextOptimizableSource(); + if (newSource == null) { + operator.source().subscribe(subscriber); + return; + } + operator = newSource; + } + } + catch (Throwable e) { + Operators.reportThrowInSubscribe(subscriber, e); + return; + } + } + + @Override + @Nullable + public abstract CoreSubscriber subscribeOrReturn(CoreSubscriber actual); + + @Override + public final CorePublisher source() { + return source; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFromPublisher.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFromPublisher.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFromPublisher.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Emits a single item at most from the source. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class MonoFromPublisher extends Mono implements Scannable, + OptimizableOperator { + + final Publisher source; + + @Nullable + final OptimizableOperator optimizableOperator; + + MonoFromPublisher(Publisher source) { + this.source = Objects.requireNonNull(source, "publisher"); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + try { + CoreSubscriber subscriber = subscribeOrReturn(actual); + if (subscriber == null) { + return; + } + source.subscribe(subscriber); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable { + return new MonoNext.NextSubscriber<>(actual); + } + + @Override + public final CorePublisher source() { + return this; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + @Nullable + public Object scanUnsafe(Scannable.Attr key) { + if (key == Scannable.Attr.PARENT) { + return source; + } + if (key == Scannable.Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoFunctions.kt =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoFunctions.kt (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoFunctions.kt (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +@file:JvmName("MonoWhenFunctionsKt") // TODO Remove in next major version +package reactor.core.publisher + +import org.reactivestreams.Publisher + +/** + * Aggregates this [Iterable] of void [Publisher]s into a new [Mono]. + * An alias for a corresponding [Mono.when] to avoid use of `when`, which is a keyword in Kotlin. + * + * TODO Move to MonoExtensions.kt in next major version + * + * @author DoHyung Kim + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("whenComplete()", "reactor.kotlin.core.publisher.whenComplete")) +fun Iterable>.whenComplete(): Mono = Mono.`when`(this) + +/** + * Merges this [Iterable] of [Mono]s into a new [Mono] by combining them + * with [combinator]. + * + * TODO Move to MonoExtensions.kt in next major version + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("zip(combinator)", "reactor.kotlin.core.publisher.zip")) +@Suppress("UNCHECKED_CAST") +inline fun Iterable>.zip(crossinline combinator: (List) -> R): Mono = + Mono.zip(this) { combinator(it.asList() as List) } + +/** + * Aggregates the given void [Publisher]s into a new void [Mono]. + * An alias for a corresponding [Mono.when] to avoid use of `when`, which is a keyword in Kotlin. + * + * @author DoHyung Kim + * @author Sebastien Deleuze + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("whenComplete(*sources)", "reactor.kotlin.core.publisher.whenComplete")) +fun whenComplete(vararg sources: Publisher<*>): Mono = MonoBridges.`when`(sources) + +/** + * Aggregates the given [Mono]s into a new [Mono]. + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions", + ReplaceWith("zip(*monos, combinator)", "reactor.kotlin.core.publisher.zip")) +@Suppress("UNCHECKED_CAST") +fun zip(vararg monos: Mono<*>, combinator: (Array<*>) -> R): Mono = + MonoBridges.zip(combinator, monos) \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoHandle.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoHandle.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoHandle.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016-2021 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.BiConsumer; + +import reactor.core.CoreSubscriber; + +/** + * Maps the values of the source publisher one-on-one via a mapper function. If the result is not {code null} then the + * {@link Mono} will complete with this value. If the result of the function is {@code null} then the {@link Mono} + * will complete without a value. + * + * @param the source value type + * @param the result value type + * @see Reactive-Streams-Commons + */ +final class MonoHandle extends InternalMonoOperator { + + final BiConsumer> handler; + + MonoHandle(Mono source, BiConsumer> handler) { + super(source); + this.handler = Objects.requireNonNull(handler, "handler"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxHandle.HandleSubscriber<>(actual, handler); + } + + @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/MonoHandleFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoHandleFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoHandleFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016-2021 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.BiConsumer; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Maps the values of the source publisher one-on-one via a mapper function. + *

+ * This variant allows composing fuseable stages. + * + * @param the source value type + * @param the result value type + * @see Reactive-Streams-Commons + */ +final class MonoHandleFuseable extends InternalMonoOperator + implements Fuseable { + + final BiConsumer> handler; + + MonoHandleFuseable(Mono source, BiConsumer> handler) { + super(source); + this.handler = Objects.requireNonNull(handler, "handler"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxHandleFuseable.HandleFuseableSubscriber<>(actual, handler); + } + + @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/MonoHasElement.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElement.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElement.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2016-2021 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.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * @see Reactive-Streams-Commons + */ +final class MonoHasElement extends InternalMonoOperator implements Fuseable { + + MonoHasElement(Mono source) { + super(source); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new HasElementSubscriber<>(actual); + } + + static final class HasElementSubscriber + extends Operators.MonoSubscriber { + Subscription s; + + 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; + } + return super.scanUnsafe(key); + } + + @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); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + //here we avoid the cancel because the source is assumed to be a Mono + complete(true); + } + + @Override + public void onComplete() { + complete(false); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElements.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElements.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoHasElements.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016-2021 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.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * @see Reactive-Streams-Commons + */ +final class MonoHasElements extends MonoFromFluxOperator + implements Fuseable { + + MonoHasElements(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new HasElementsSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class HasElementsSubscriber extends Operators.MonoSubscriber { + Subscription s; + + 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; + + return super.scanUnsafe(key); + } + + @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); + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + s.cancel(); + + complete(true); + } + + @Override + public void onComplete() { + complete(false); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoHide.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoHide.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoHide.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016-2021 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.Scannable; + +/** + * Wraps another Publisher/Mono and hides its identity, including its + * Subscription. + * + *

+ * @see Reactive-Streams-Commons + * + * @param the value type + * + */ +final class MonoHide extends InternalMonoOperator { + + MonoHide(Mono source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxHide.HideSubscriber<>(actual); + } + + @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/MonoIgnoreElement.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreElement.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreElement.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Ignores normal values and passes only the terminal signals along. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoIgnoreElement extends InternalMonoOperator { + + MonoIgnoreElement(Mono source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new MonoIgnoreElements.IgnoreElementsSubscriber<>(actual); + } + + @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/MonoIgnoreElements.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreElements.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreElements.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016-2021 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.util.annotation.Nullable; + +/** + * Ignores normal values and passes only the terminal signals along. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoIgnoreElements extends MonoFromFluxOperator { + + MonoIgnoreElements(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new IgnoreElementsSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class IgnoreElementsSubscriber implements InnerOperator { + final CoreSubscriber actual; + + Subscription s; + + IgnoreElementsSubscriber(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; + + 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); + } + } + + @Override + public void onNext(T t) { + // deliberately ignored + Operators.onDiscard(t, actual.currentContext()); //FIXME cache context + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + // requests Long.MAX_VALUE anyway + } + + @Override + public void cancel() { + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnorePublisher.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnorePublisher.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnorePublisher.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Ignores normal values and passes only the terminal signals along. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoIgnorePublisher extends Mono implements Scannable, + OptimizableOperator { + + final Publisher source; + + @Nullable + final OptimizableOperator optimizableOperator; + + MonoIgnorePublisher(Publisher source) { + this.source = Objects.requireNonNull(source, "publisher"); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + @Override + public void subscribe(CoreSubscriber actual) { + try { + source.subscribe(subscribeOrReturn(actual)); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + } + + @Override + public final CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable { + return new MonoIgnoreElements.IgnoreElementsSubscriber<>(actual); + } + + @Override + public final CorePublisher source() { + return this; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + @Nullable + public Object scanUnsafe(Scannable.Attr key) { + if (key == Scannable.Attr.PARENT) { + return source; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreThen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreThen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoIgnoreThen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Concatenates a several Mono sources with a final Mono source by + * ignoring values from the first set of sources and emitting the value + * the last Mono source generates. + * + * @param the final value type + */ +final class MonoIgnoreThen extends Mono implements Scannable { + + final Publisher[] ignore; + + final Mono last; + + MonoIgnoreThen(Publisher[] ignore, Mono last) { + this.ignore = Objects.requireNonNull(ignore, "ignore"); + this.last = Objects.requireNonNull(last, "last"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + ThenIgnoreMain manager = new ThenIgnoreMain<>(actual, this.ignore, this.last); + actual.onSubscribe(manager); + manager.subscribeNext(); + } + + /** + * Shifts the current last Mono into the ignore array and sets up a new last Mono instance. + * @param the new last value type + * @param newLast the new last Mono instance + * @return the new operator set up + */ + MonoIgnoreThen shift(Mono newLast) { + Objects.requireNonNull(newLast, "newLast"); + Publisher[] a = this.ignore; + int n = a.length; + Publisher[] b = new Publisher[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = this.last; + + return new MonoIgnoreThen<>(b, newLast); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class ThenIgnoreMain implements InnerOperator { + + final Publisher[] ignoreMonos; + final Mono lastMono; + final CoreSubscriber actual; + + T value; + + int index; + Subscription activeSubscription; + boolean done; + + volatile int state; + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(ThenIgnoreMain.class, "state"); + // The following are to be used as bit masks, not as values per se. + static final int HAS_REQUEST = 0b00000010; + static final int HAS_SUBSCRIPTION = 0b00000100; + static final int HAS_VALUE = 0b00001000; + static final int HAS_COMPLETION = 0b00010000; + // The following are to be used as value (ie using == or !=). + static final int CANCELLED = 0b10000000; + + ThenIgnoreMain(CoreSubscriber subscriber, + Publisher[] ignoreMonos, Mono lastMono) { + this.actual = subscriber; + this.ignoreMonos = ignoreMonos; + this.lastMono = lastMono; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return this.activeSubscription; + if (key == Attr.CANCELLED) return isCancelled(this.state); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return this.actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.activeSubscription, s)) { + this.activeSubscription = s; + + final int previousState = markHasSubscription(); + if (isCancelled(previousState)) { + s.cancel(); + return; + } + + s.request(Long.MAX_VALUE); + } + } + + @Override + public void cancel() { + int previousState = markCancelled(); + + if (hasSubscription(previousState)) { + this.activeSubscription.cancel(); + } + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + for (; ; ) { + final int state = this.state; + if (isCancelled(state)) { + return; + } + if (hasRequest(state)) { + return; + } + if (STATE.compareAndSet(this, state, state | HAS_REQUEST)) { + if (hasValue(state)) { + final CoreSubscriber actual = this.actual; + final T v = this.value; + + actual.onNext(v); + actual.onComplete(); + } + return; + } + } + } + } + + @Override + public void onNext(T t) { + if (this.done) { + Operators.onDiscard(t, currentContext()); + return; + } + + if (this.index != this.ignoreMonos.length) { + // ignored + Operators.onDiscard(t, currentContext()); + return; + } + + this.done = true; + + complete(t); + } + + @Override + public void onComplete() { + if (this.done) { + return; + } + + if (this.index != this.ignoreMonos.length) { + final int previousState = markUnsubscribed(); + if (isCancelled(previousState)) { + return; + } + this.activeSubscription = null; + this.index++; + subscribeNext(); + return; + } + + this.done = true; + + this.actual.onComplete(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + void subscribeNext() { + final Publisher[] a = this.ignoreMonos; + + for (;;) { + final int i = this.index; + + if (i == a.length) { + Mono m = this.lastMono; + if (m instanceof Callable) { + T v; + try { + v = ((Callable)m).call(); + } + catch (Throwable ex) { + onError(Operators.onOperatorError(ex, currentContext())); + return; + } + + if (v != null) { + onNext(v); + } + onComplete(); + } else { + m.subscribe(this); + } + return; + } else { + final Publisher m = a[i]; + + if (m instanceof Callable) { + try { + Operators.onDiscard(((Callable) m).call(), currentContext()); + } + catch (Throwable ex) { + onError(Operators.onOperatorError(ex, currentContext())); + return; + } + + this.index = i + 1; + continue; + } + + m.subscribe((CoreSubscriber) this); + return; + } + } + } + + @Override + public void onError(Throwable t) { + if (this.done) { + Operators.onErrorDropped(t, actual().currentContext()); + return; + } + + this.done = true; + + this.actual.onError(t); + } + + final void complete(T value) { + for (; ; ) { + int s = this.state; + if (isCancelled(s)) { + Operators.onDiscard(value, this.actual.currentContext()); + return; + } + + if (hasRequest(s) && STATE.compareAndSet(this, s, s | (HAS_VALUE | HAS_COMPLETION))) { + final CoreSubscriber actual = this.actual; + + actual.onNext(value); + actual.onComplete(); + return; + } + + this.value = value; + if (STATE.compareAndSet(this, s, s | (HAS_VALUE | HAS_COMPLETION))) { + return; + } + } + } + + final int markHasSubscription() { + for (;;) { + final int state = this.state; + + if (state == CANCELLED) { + return state; + } + + if ((state & HAS_SUBSCRIPTION) == HAS_SUBSCRIPTION) { + return state; + } + + if (STATE.compareAndSet(this, state, state | HAS_SUBSCRIPTION)) { + return state; + } + } + } + + final int markUnsubscribed() { + for (;;) { + final int state = this.state; + + if (isCancelled(state)) { + return state; + } + + if (!hasSubscription(state)) { + return state; + } + + if (STATE.compareAndSet(this, state, state &~ HAS_SUBSCRIPTION)) { + return state; + } + } + } + + final int markCancelled() { + for (;;) { + final int state = this.state; + + if (state == CANCELLED) { + return state; + } + + if (STATE.compareAndSet(this, state, CANCELLED)) { + return state; + } + } + } + + static boolean isCancelled(int s) { + return s == CANCELLED; + } + + static boolean hasSubscription(int s) { + return (s & HAS_SUBSCRIPTION) == HAS_SUBSCRIPTION; + } + + static boolean hasRequest(int s) { + return (s & HAS_REQUEST) == HAS_REQUEST; + } + + static boolean hasValue(int s) { + return (s & HAS_VALUE) == HAS_VALUE; + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoJust.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoJust.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoJust.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015-2021 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 reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * @see Reactive-Streams-Commons + */ +final class MonoJust +extends Mono + implements Fuseable.ScalarCallable, Fuseable, SourceProducer { + + final T value; + + MonoJust(T value) { + this.value = Objects.requireNonNull(value, "value"); + } + + @Override + public T call() throws Exception { + return value; + } + + @Override + public T block(Duration m) { + return value; + } + + @Override + public T block() { + return value; + } + + @Override + public void subscribe(CoreSubscriber actual) { + actual.onSubscribe(Operators.scalarSubscription(actual, value)); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.BUFFERED) return 1; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoLift.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoLift.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoLift.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; + +/** + * @author Stephane Maldini + */ +final class MonoLift extends InternalMonoOperator { + + final Operators.LiftFunction liftFunction; + + MonoLift(Publisher p, + Operators.LiftFunction liftFunction) { + super(Mono.from(p)); + this.liftFunction = liftFunction; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + CoreSubscriber input = + liftFunction.lifter.apply(source, actual); + + Objects.requireNonNull(input, "Lifted subscriber MUST NOT be null"); + + return input; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return super.stepName(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); + if (key == Attr.LIFTER) return liftFunction.name; + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoLiftFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoLiftFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoLiftFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; + +/** + * @author Stephane Maldini + * @author Simon Baslé + */ +final class MonoLiftFuseable extends InternalMonoOperator + implements Fuseable { + + final Operators.LiftFunction liftFunction; + + MonoLiftFuseable(Publisher p, + Operators.LiftFunction liftFunction) { + super(Mono.from(p)); + this.liftFunction = liftFunction; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return super.stepName(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Scannable.from(source).scanUnsafe(key); + if (key == Attr.LIFTER) return liftFunction.name; + return super.scanUnsafe(key); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + + CoreSubscriber input = liftFunction.lifter.apply(source, actual); + + Objects.requireNonNull(input, "Lifted subscriber MUST NOT be null"); + + 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 + return input; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoLog.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoLog.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoLog.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016-2021 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.ConditionalSubscriber; +import reactor.core.publisher.FluxPeekFuseable.PeekConditionalSubscriber; + +/** + * Peek into the lifecycle events and signals of a sequence. + *

+ *

+ * The callbacks are all optional. + *

+ *

+ * Crashes by the lambdas are ignored. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoLog extends InternalMonoOperator { + + final SignalPeek log; + + MonoLog(Mono source, SignalPeek log) { + super(source); + this.log = log; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + @SuppressWarnings("unchecked") // javac, give reason to suppress because inference anomalies + ConditionalSubscriber s2 = (ConditionalSubscriber) actual; + return new PeekConditionalSubscriber<>(s2, log); + } + return new FluxPeek.PeekSubscriber<>(actual, log); + } + + @Override + public Object scanUnsafe(Attr key) { + 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/MonoLogFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoLogFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoLogFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Peek into the lifecycle events and signals of a sequence. + *

+ *

+ * The callbacks are all optional. + *

+ *

+ * Crashes by the lambdas are ignored. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class MonoLogFuseable extends InternalMonoOperator + implements Fuseable { + + final SignalPeek log; + + MonoLogFuseable(Mono source, SignalPeek log) { + super(source); + this.log = log; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new FluxPeekFuseable.PeekFuseableConditionalSubscriber<>((ConditionalSubscriber) actual, log); + } + return new FluxPeekFuseable.PeekFuseableSubscriber<>(actual, log); + } + + @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/MonoMap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoMap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoMap.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Maps the values of the source publisher one-on-one via a mapper function. + * + * @param the source value type + * @param the result value type + * @see Reactive-Streams-Commons + */ +final class MonoMap extends InternalMonoOperator { + + final Function mapper; + + /** + * Constructs a StreamMap instance with the given source and mapper. + * + * @param source the source Publisher instance + * @param mapper the mapper function + * @throws NullPointerException if either {@code source} or {@code mapper} is null. + */ + MonoMap(Mono source, Function mapper) { + super(source); + this.mapper = Objects.requireNonNull(mapper, "mapper"); + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof Fuseable.ConditionalSubscriber) { + Fuseable.ConditionalSubscriber cs = (Fuseable.ConditionalSubscriber) actual; + return new FluxMap.MapConditionalSubscriber<>(cs, mapper); + } + return new FluxMap.MapSubscriber<>(actual, mapper); + } + + @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/MonoMapFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoMapFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoMapFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.core.Fuseable; + +/** + * Maps the values of the source publisher one-on-one via a mapper function. + *

+ * This variant allows composing fuseable stages. + * + * @param the source value type + * @param the result value type + * @see Reactive-Streams-Commons + */ +final class MonoMapFuseable extends InternalMonoOperator + implements Fuseable { + + final Function mapper; + + /** + * Constructs a StreamMap instance with the given source and mapper. + * + * @param source the source Publisher instance + * @param mapper the mapper function + * @throws NullPointerException if either {@code source} or {@code mapper} is null. + */ + MonoMapFuseable(Mono source, Function mapper) { + super(source); + this.mapper = Objects.requireNonNull(mapper, "mapper"); + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + ConditionalSubscriber cs = (ConditionalSubscriber) actual; + return new FluxMapFuseable.MapFuseableConditionalSubscriber<>(cs, mapper); + } + return new FluxMapFuseable.MapFuseableSubscriber<>(actual, mapper); + } + + @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/MonoMaterialize.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoMaterialize.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoMaterialize.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2016-2021 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.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * @author Stephane Maldini + */ +final class MonoMaterialize extends InternalMonoOperator> { + + MonoMaterialize(Mono source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new MaterializeSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class MaterializeSubscriber implements InnerOperator> { + + final CoreSubscriber> actual; + + /** + * Allows to distinguish onNext+onComplete vs onComplete (ignore the complete in the first case) + * while still being able to null out {@link #signalToReplayUponFirstRequest} below. + */ + boolean alreadyReceivedSignalFromSource; + Subscription s; + + /** + * Captures the fact that we were requested. The amount or the number of request + * calls doesn't matter since the source is a Mono. + */ + volatile boolean requested; + /** + * This captures an early termination (onComplete or onError), with the goal to + * avoid capturing onNext, so as to simplify cleanup and avoid discard concerns. + */ + @Nullable + volatile Signal signalToReplayUponFirstRequest; + + MaterializeSubscriber(CoreSubscriber> actual) { + this.actual = actual; + } + + @Override + public CoreSubscriber> actual() { + return this.actual; + } + + @Nullable + @Override + 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 void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (alreadyReceivedSignalFromSource || !requested) { + //protocol error: there was an onNext, onComplete or onError before (which are all breaking RS or Mono contract) + Operators.onNextDropped(t, currentContext()); + return; + } + alreadyReceivedSignalFromSource = true; + Signal signal = Signal.next(t, currentContext()); + actual.onNext(signal); + actual.onComplete(); + } + + @Override + public void onError(Throwable throwable) { + if (alreadyReceivedSignalFromSource) { + //protocol error: there was an onNext, onComplete or onError before (which are all breaking RS or Mono contract) + Operators.onErrorDropped(throwable, currentContext()); + return; + } + alreadyReceivedSignalFromSource = true; + signalToReplayUponFirstRequest = Signal.error(throwable, currentContext()); + drain(); + } + + @Override + public void onComplete() { + if (alreadyReceivedSignalFromSource) { + //either protocol error, or there was an `onNext` (in which case we already did the job) + return; + } + alreadyReceivedSignalFromSource = true; + signalToReplayUponFirstRequest = Signal.complete(currentContext()); + drain(); + } + + boolean drain() { + final Signal signal = signalToReplayUponFirstRequest; + if (signal != null && requested) { + actual.onNext(signal); + actual.onComplete(); + signalToReplayUponFirstRequest = null; + return true; + } + return false; + } + + @Override + public void request(long l) { + if (!this.requested && Operators.validate(l)) { + this.requested = true; //ignore further requests + if (drain()) { + return; //there was an early completion + } + s.request(l); + } + } + + @Override + public void cancel() { + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoMetrics.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoMetrics.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoMetrics.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2018-2021 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.core.instrument.*; +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. + * + * @implNote Metrics.isInstrumentationAvailable() test should be performed BEFORE instantiating + * or referencing this class, otherwise a {@link NoClassDefFoundError} will be thrown if + * Micrometer is not there. + * + * @author Simon Baslé + * @author Stephane Maldini + */ +final class MonoMetrics extends InternalMonoOperator { + + final String name; + final Tags tags; + + final MeterRegistry registryCandidate; + + MonoMetrics(Mono mono) { + super(mono); + + this.name = resolveName(mono); + this.tags = resolveTags(mono, DEFAULT_TAGS_MONO); + + this.registryCandidate = Metrics.MicrometerConfiguration.getRegistry(); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new MetricsSubscriber<>(actual, registryCandidate, Clock.SYSTEM, this.name, this.tags); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static class MetricsSubscriber implements InnerOperator { + + final CoreSubscriber actual; + final Clock clock; + final String sequenceName; + final Tags commonTags; + final MeterRegistry registry; + + Timer.Sample subscribeToTerminateSample; + boolean done; + Subscription s; + + MetricsSubscriber(CoreSubscriber actual, + MeterRegistry registry, Clock clock, String sequenceName, Tags commonTags) { + this.actual = actual; + this.clock = clock; + this.sequenceName = sequenceName; + this.commonTags = commonTags; + this.registry = registry; + } + + @Override + final public CoreSubscriber actual() { + return actual; + } + + @Override + final public void cancel() { + recordCancel(sequenceName, commonTags, registry, subscribeToTerminateSample); + s.cancel(); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + recordOnCompleteEmpty(sequenceName, commonTags, registry, subscribeToTerminateSample); + actual.onComplete(); + } + + @Override + final public void onError(Throwable e) { + if (done) { + recordMalformed(sequenceName, commonTags, registry); + Operators.onErrorDropped(e, actual.currentContext()); + return; + } + done = true; + recordOnError(sequenceName, commonTags, registry, subscribeToTerminateSample, e); + actual.onError(e); + } + + @Override + public void onNext(T t) { + if (done) { + recordMalformed(sequenceName, commonTags, registry); + Operators.onNextDropped(t, actual.currentContext()); + return; + } + done = true; + 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); + this.subscribeToTerminateSample = Timer.start(clock); + this.s = s; + actual.onSubscribe(this); + } + } + + @Override + final public void request(long l) { + if (Operators.validate(l)) { + s.request(l); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return InnerOperator.super.scanUnsafe(key); + } + + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoMetricsFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoMetricsFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoMetricsFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2018-2021 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.core.instrument.Clock; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Timer; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +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. + + * @implNote Metrics.isInstrumentationAvailable() test should be performed BEFORE instantiating or referencing this + * class, otherwise a {@link NoClassDefFoundError} will be thrown if Micrometer is not there. + * + * @author Simon Baslé + * @author Stephane Maldini + */ +final class MonoMetricsFuseable extends InternalMonoOperator implements Fuseable { + + final String name; + final Tags tags; + + final MeterRegistry registryCandidate; + + MonoMetricsFuseable(Mono mono) { + super(mono); + + this.name = resolveName(mono); + this.tags = resolveTags(mono, FluxMetrics.DEFAULT_TAGS_MONO); + + this.registryCandidate = Metrics.MicrometerConfiguration.getRegistry();; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new MetricsFuseableSubscriber<>(actual, registryCandidate, Clock.SYSTEM, this.name, this.tags); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + /** + * @param + * + * @implNote we don't want to particularly track fusion-specific calls, as this subscriber would only detect fused + * subsequences immediately upstream of it, so Counters would be a bit irrelevant. We however want to instrument + * onNext counts. + */ + static final class MetricsFuseableSubscriber extends MonoMetrics.MetricsSubscriber + implements Fuseable, QueueSubscription { + + int mode; + + @Nullable + Fuseable.QueueSubscription qs; + + MetricsFuseableSubscriber(CoreSubscriber actual, + MeterRegistry registry, + Clock clock, + String sequenceName, + Tags sequenceTags) { + super(actual, registry, clock, sequenceName, sequenceTags); + } + + @Override + public void clear() { + if (qs != null) { + qs.clear(); + } + } + + @Override + public void onComplete() { + if (mode == ASYNC) { + if (!done) { //if it was valued, taken care of in poll() + FluxMetrics.recordOnCompleteEmpty(sequenceName, commonTags, registry, subscribeToTerminateSample); + } + actual.onComplete(); + } + else { + if (done) { + return; + } + done = true; + FluxMetrics.recordOnCompleteEmpty(sequenceName, commonTags, registry, subscribeToTerminateSample); + actual.onComplete(); + } + } + + @Override + public void onNext(T t) { + if (mode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + FluxMetrics.recordMalformed(sequenceName, commonTags, registry); + Operators.onNextDropped(t, actual.currentContext()); + return; + } + done = true; + FluxMetrics.recordOnComplete(sequenceName, + commonTags, + registry, + subscribeToTerminateSample); + actual.onNext(t); + actual.onComplete(); + } + } + + @Override + public boolean isEmpty() { + return qs == null || qs.isEmpty(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + FluxMetrics.recordOnSubscribe(sequenceName, commonTags, registry); + this.subscribeToTerminateSample = Timer.start(clock); + this.qs = Operators.as(s); + this.s = s; + actual.onSubscribe(this); + + } + } + + @Override + @Nullable + public T poll() { + if (qs == null) { + return null; + } + try { + T v = qs.poll(); + if (!done) { + if (v == null && mode == SYNC) { + FluxMetrics.recordOnCompleteEmpty(sequenceName, commonTags, registry, subscribeToTerminateSample); + } + else if (v != null) { + FluxMetrics.recordOnComplete(sequenceName, commonTags, registry, subscribeToTerminateSample); + } + } + done = true; + return v; + } + catch (Throwable e) { + FluxMetrics.recordOnError(sequenceName, commonTags, registry, subscribeToTerminateSample, e); + throw e; + } + } + + @Override + public int requestFusion(int mode) { + //Simply negotiate the fusion by delegating: + if (qs != null) { + this.mode = qs.requestFusion(mode); + return this.mode; + } + return Fuseable.NONE; //should not happen unless requestFusion called before subscribe + } + + @Override + public int size() { + return qs == null ? 0 : qs.size(); + } + + @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/MonoName.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoName.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoName.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2016-2021 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.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +/** + * An operator that just bears a name or a set of tags, which can be retrieved via the + * {@link Attr#TAGS TAGS} + * attribute. + * + * @author Stephane Maldini + */ +final class MonoName extends InternalMonoOperator { + + final String name; + + final Set> tags; + + @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); + } + if (source instanceof MonoNameFuseable) { + MonoNameFuseable s = (MonoNameFuseable) source; + return new MonoNameFuseable<>(s.source, name, s.tags); + } + 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)); + + if (source instanceof MonoName) { + MonoName s = (MonoName) source; + if(s.tags != null) { + tags = new HashSet<>(tags); + tags.addAll(s.tags); + } + 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); + } + return new MonoNameFuseable<>(s.source, s.name, tags); + } + if (source instanceof Fuseable) { + return new MonoNameFuseable<>(source, null, tags); + } + return new MonoName<>(source, null, tags); + } + + MonoName(Mono source, + @Nullable String name, + @Nullable Set> tags) { + super(source); + this.name = name; + this.tags = tags; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.NAME) { + return name; + } + + if (key == Attr.TAGS && tags != null) { + return tags.stream(); + } + + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return super.scanUnsafe(key); + } + + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoNameFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoNameFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoNameFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016-2021 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.Set; + +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 + * {@link Attr#TAGS TAGS} + * attribute. + * + * @author Stephane Maldini + */ +final class MonoNameFuseable extends InternalMonoOperator implements Fuseable { + + final String name; + + final Set> tags; + + MonoNameFuseable(Mono source, + @Nullable String name, + @Nullable Set> tags) { + super(source); + this.name = name; + this.tags = tags; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.NAME) { + return name; + } + + if (key == Attr.TAGS && tags != null) { + return tags.stream(); + } + + if (key == RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return super.scanUnsafe(key); + } + + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoNever.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoNever.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoNever.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Represents an never publisher which only calls onSubscribe. + *

+ * This Publisher is effectively stateless and only a single instance exists. + * Use the {@link #instance()} method to obtain a properly type-parametrized view of it. + * + * @see Reactive-Streams-Commons + */ +final class MonoNever +extends Mono implements SourceProducer { + + static final Mono INSTANCE = new MonoNever(); + + MonoNever() { + // deliberately no op + } + + @Override + public void subscribe(CoreSubscriber actual) { + actual.onSubscribe(Operators.emptySubscription()); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + /** + * Returns a properly parametrized instance of this never Publisher. + * + * @param the value type + * @return a properly parametrized instance of this never Publisher + */ + @SuppressWarnings("unchecked") + static Mono instance() { + return (Mono) INSTANCE; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoNext.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoNext.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoNext.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Emits a single item at most from the source. + * + * @param the value type + * + * @see Reactive-Streams-Commons + */ +final class MonoNext extends MonoFromFluxOperator { + + MonoNext(Flux source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new NextSubscriber<>(actual); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class NextSubscriber implements InnerOperator { + + final CoreSubscriber actual; + + Subscription s; + + boolean done; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(NextSubscriber.class, "wip"); + + NextSubscriber(CoreSubscriber actual) { + this.actual = actual; + } + + @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; + } + + s.cancel(); + actual.onNext(t); + onComplete(); + } + + @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; + actual.onComplete(); + } + + @Override + public void request(long n) { + if (WIP.compareAndSet(this, 0, 1)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void cancel() { + s.cancel(); + } + + @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 InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoOnAssembly.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoOnAssembly.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoOnAssembly.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016-2021 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.publisher.FluxOnAssembly.AssemblySnapshot; +import reactor.util.annotation.Nullable; + +/** + * Captures the current stacktrace when this publisher is created and makes it + * available/visible for debugging purposes from the inner Subscriber. + *

+ * Note that getting a stacktrace is a costly operation. + *

+ * The operator sanitizes the stacktrace and removes noisy entries such as:

    + *
  • java.lang.Thread entries
  • method references with source line of 1 (bridge + * methods)
  • Tomcat worker thread entries
  • JUnit setup
+ * + * @param the value type passing through + * @see https://github.com/reactor/reactive-streams-commons + */ +final class MonoOnAssembly extends InternalMonoOperator implements Fuseable, + AssemblyOp { + + final AssemblySnapshot stacktrace; + + /** + * Create an assembly trace exposed as a {@link Mono}. + */ + MonoOnAssembly(Mono source, AssemblySnapshot stacktrace) { + super(source); + this.stacktrace = stacktrace; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + @SuppressWarnings("unchecked") ConditionalSubscriber cs = + (ConditionalSubscriber) actual; + return new FluxOnAssembly.OnAssemblyConditionalSubscriber<>(cs, stacktrace, source, this); + } + else { + return new FluxOnAssembly.OnAssemblySubscriber<>(actual, stacktrace, source, this); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.ACTUAL_METADATA) return !stacktrace.isCheckpoint; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public String stepName() { + return stacktrace.operatorAssemblyInformation(); + } + + @Override + public String toString() { + return stacktrace.operatorAssemblyInformation(); + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoOnErrorResume.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoOnErrorResume.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoOnErrorResume.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; + +/** + * Resumes the failed main sequence with another sequence returned by + * a function for the particular failure exception. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoOnErrorResume extends InternalMonoOperator { + + final Function> nextFactory; + + MonoOnErrorResume(Mono source, + Function> + nextFactory) { + super(source); + this.nextFactory = Objects.requireNonNull(nextFactory, "nextFactory"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxOnErrorResume.ResumeSubscriber<>(actual, nextFactory); + } + + @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/MonoOperator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoOperator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoOperator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016-2021 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.BiFunction; +import java.util.function.Function; + + +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * 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}. + * + * @param delegate {@link Publisher} type + * @param produced type + */ +public abstract class MonoOperator extends Mono implements Scannable { + + protected final Mono source; + + /** + * Build a {@link MonoOperator} wrapper around the passed parent {@link Publisher} + * + * @param source the {@link Publisher} to decorate + */ + protected MonoOperator(Mono source) { + this.source = Objects.requireNonNull(source); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.PARENT) return source; + return null; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoPeek.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoPeek.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoPeek.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable.ConditionalSubscriber; +import reactor.core.publisher.FluxPeekFuseable.PeekConditionalSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Peeks out values that make a filter function return false. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoPeek extends InternalMonoOperator implements SignalPeek { + + final Consumer onSubscribeCall; + + final Consumer onNextCall; + + final LongConsumer onRequestCall; + + final Runnable onCancelCall; + + MonoPeek(Mono source, + @Nullable Consumer onSubscribeCall, + @Nullable Consumer onNextCall, + @Nullable LongConsumer onRequestCall, + @Nullable Runnable onCancelCall) { + super(source); + this.onSubscribeCall = onSubscribeCall; + this.onNextCall = onNextCall; + this.onRequestCall = onRequestCall; + this.onCancelCall = onCancelCall; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new PeekConditionalSubscriber<>( + (ConditionalSubscriber) actual, this); + } + return new FluxPeek.PeekSubscriber<>(actual, this); + } + + @Override + @Nullable + public Consumer onSubscribeCall() { + return onSubscribeCall; + } + + @Override + @Nullable + public Consumer onNextCall() { + return onNextCall; + } + + @Override + @Nullable + public Consumer onErrorCall() { + return null; + } + + @Override + @Nullable + public Runnable onCompleteCall() { + return null; + } + + @Override + @Nullable + public Runnable onAfterTerminateCall() { + return null; + } + + @Override + @Nullable + public LongConsumer onRequestCall() { + return onRequestCall; + } + + @Override + @Nullable + public Runnable onCancelCall() { + return onCancelCall; + } + + @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/MonoPeekFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoPeekFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoPeekFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Peeks out values that make a filter function return false. + * + * @param the value type + * @see Reactive-Streams-Commons + * + */ +final class MonoPeekFuseable extends InternalMonoOperator + implements Fuseable, SignalPeek { + + final Consumer onSubscribeCall; + + final Consumer onNextCall; + + final LongConsumer onRequestCall; + + final Runnable onCancelCall; + + MonoPeekFuseable(Mono source, + @Nullable Consumer onSubscribeCall, + @Nullable Consumer onNextCall, + @Nullable LongConsumer onRequestCall, + @Nullable Runnable onCancelCall) { + super(source); + + this.onSubscribeCall = onSubscribeCall; + this.onNextCall = onNextCall; + this.onRequestCall = onRequestCall; + this.onCancelCall = onCancelCall; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new FluxPeekFuseable.PeekFuseableConditionalSubscriber<>((ConditionalSubscriber) actual, this); + } + return new FluxPeekFuseable.PeekFuseableSubscriber<>(actual, this); + } + + @Override + @Nullable + public Consumer onSubscribeCall() { + return onSubscribeCall; + } + + @Override + @Nullable + public Consumer onNextCall() { + return onNextCall; + } + + @Override + @Nullable + public Consumer onErrorCall() { + return null; + } + + @Override + @Nullable + public Runnable onCompleteCall() { + return null; + } + + @Override + @Nullable + public Runnable onAfterTerminateCall() { + return null; + } + + @Override + @Nullable + public LongConsumer onRequestCall() { + return onRequestCall; + } + + @Override + @Nullable + public Runnable onCancelCall() { + return onCancelCall; + } + + @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 --- 3rdParty_sources/reactor/reactor/core/publisher/MonoPeekTerminal.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoPeekTerminal.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2016-2021 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.BiConsumer; +import java.util.function.Consumer; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Peeks the value of a {@link Mono} and execute terminal callbacks accordingly, allowing + * to distinguish between cases where the Mono was empty, valued or errored. + * + * @param the value type + * + * @author Simon Baslé + * @see Reactive-Streams-Commons + */ +final class MonoPeekTerminal extends InternalMonoOperator implements Fuseable { + + final BiConsumer onAfterTerminateCall; + final Consumer onSuccessCall; + final Consumer onErrorCall; + + MonoPeekTerminal(Mono source, + @Nullable Consumer onSuccessCall, + @Nullable Consumer onErrorCall, + @Nullable BiConsumer onAfterTerminateCall) { + super(source); + this.onAfterTerminateCall = onAfterTerminateCall; + this.onSuccessCall = onSuccessCall; + this.onErrorCall = onErrorCall; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + if (actual instanceof ConditionalSubscriber) { + return new MonoTerminalPeekSubscriber<>((ConditionalSubscriber) actual, + this); + } + return new MonoTerminalPeekSubscriber<>(actual, this); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + /* + The specificity of this operator's subscriber is that it is implemented as a single + class for all cases (fuseable or not, conditional or not). So subscription and actual + are duplicated to arrange for the special cases (QueueSubscription and ConditionalSubscriber). + + A challenge for Fuseable: classes that rely only on `instanceof Fuseable` will always + think this operator is Fuseable, when they should also check `requestFusion`. This is + the case with StepVerifier in 3.0.3 for instance, but actual operators should otherwise + also call requestFusion, which will return NONE if the source isn't Fuseable. + + A challenge for ConditionalSubscriber: since there is no `requestConditional` here, + the operators only rely on `instanceof`... So this subscriber will always seem conditional. + As a result, if the `tryOnNext` method is invoked while the `actualConditional` is null, + it falls back to calling `onNext` directly. + */ + static final class MonoTerminalPeekSubscriber + implements ConditionalSubscriber, InnerOperator, + Fuseable.QueueSubscription { + + final CoreSubscriber actual; + final ConditionalSubscriber actualConditional; + + final MonoPeekTerminal parent; + + //TODO could go into a common base for all-in-one subscribers? (as well as actual above) + Subscription s; + @Nullable + Fuseable.QueueSubscription queueSubscription; + + int sourceMode; + + volatile boolean done; + + /* `valued` serves as a guard against re-executing the callbacks in onComplete/onError + as soon as onNext has been called. So onNext will set the flag immediately, then + onNext/poll will trigger the "valued" version of ALL the callbacks (respectively + in NONE mode and SYNC/ASYNC mode). If empty, onCompleted is called without valued + being set, so it will execute the "empty" version of ALL callbacks. Same for onError. + + Having this flag also prevents callbacks to be attempted twice in the case of a + callback failure, which is forwarded to onError if it happens during onNext... + */ boolean valued; + + MonoTerminalPeekSubscriber(ConditionalSubscriber actual, + MonoPeekTerminal parent) { + this.actualConditional = actual; + this.actual = actual; + this.parent = parent; + } + + MonoTerminalPeekSubscriber(CoreSubscriber actual, + MonoPeekTerminal parent) { + this.actual = actual; + this.actualConditional = null; + this.parent = parent; + } + + @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 InnerOperator.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + s.cancel(); + } + + @Override + public void onSubscribe(Subscription s) { + this.s = s; + this.queueSubscription = Operators.as(s); //will set it to null if not Fuseable + + actual.onSubscribe(this); + } + + @Override + public void onNext(T t) { + if (sourceMode == ASYNC) { + actual.onNext(null); + } + else { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + //implementation note: this operator doesn't expect the source to be anything but a Mono + //so it doesn't check that valued has been set before + valued = true; + + if (parent.onSuccessCall != null) { + try { + parent.onSuccessCall.accept(t); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, + actual.currentContext())); + return; + } + } + + actual.onNext(t); + + if (parent.onAfterTerminateCall != null) { + try { + parent.onAfterTerminateCall.accept(t, null); + } + catch (Throwable e) { + //don't invoke error callback, see https://github.com/reactor/reactor-core/issues/270 + Operators.onErrorDropped(Operators.onOperatorError(s, e, t, + actual.currentContext()), + actual.currentContext()); + } + } + } + } + + @Override + public boolean tryOnNext(T t) { + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return false; + } + if (actualConditional == null) { + onNext(t); //this is the fallback if the actual isn't actually conditional + return false; + } + + //implementation note: this operator doesn't expect the source to be anything but a Mono + //so it doesn't check that valued has been set before + valued = true; + + if (parent.onSuccessCall != null) { + try { + parent.onSuccessCall.accept(t); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, t, actual.currentContext())); + return false; + } + } + + boolean r = actualConditional.tryOnNext(t); + + if (parent.onAfterTerminateCall != null) { + try { + parent.onAfterTerminateCall.accept(t, null); + } + catch (Throwable e) { + //don't invoke error callback, see https://github.com/reactor/reactor-core/issues/270 + Operators.onErrorDropped(Operators.onOperatorError(s, e, t, + actual.currentContext()), + actual.currentContext()); + } + } + + return r; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + + Consumer onError = parent.onErrorCall; + + if (!valued && onError != null) { + try { + onError.accept(t); + } + catch (Throwable e) { + t = Operators.onOperatorError(null, e, t, actual.currentContext()); + } + } + + try { + actual.onError(t); + } + catch (UnsupportedOperationException use) { + if (onError == null || + !Exceptions.isErrorCallbackNotImplemented(use) && use.getCause() != t) { + throw use; + } + } + + if (!valued && parent.onAfterTerminateCall != null) { + try { + parent.onAfterTerminateCall.accept(null, t); + } + catch (Throwable e) { + //don't invoke error callback, see https://github.com/reactor/reactor-core/issues/270 + Operators.onErrorDropped(Operators.onOperatorError(e, + actual.currentContext()), + actual.currentContext()); + } + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + if (sourceMode == NONE && !valued) { + //TODO maybe add an onEmpty call here + if (parent.onSuccessCall != null) { + try { + parent.onSuccessCall.accept(null); + } + catch (Throwable e) { + onError(Operators.onOperatorError(s, e, actual.currentContext())); + return; + } + } + } + done = true; + + actual.onComplete(); + + if (sourceMode == NONE && !valued && parent.onAfterTerminateCall != null) { + try { + parent.onAfterTerminateCall.accept(null, null); + } + catch (Throwable e) { + //don't invoke error callback, see https://github.com/reactor/reactor-core/issues/270 + Operators.onErrorDropped(Operators.onOperatorError(e, + actual.currentContext()), + actual.currentContext()); + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public T poll() { + assert queueSubscription != null; + boolean d = done; + T v = queueSubscription.poll(); + if (!valued && (v != null || d || sourceMode == SYNC)) { + valued = true; + //TODO include onEmptyCall here as well? + if (parent.onSuccessCall != null) { + try { + parent.onSuccessCall.accept(v); + } + catch (Throwable e) { + throw Exceptions.propagate(Operators.onOperatorError(s, e, v, + actual.currentContext())); + } + } + if (parent.onAfterTerminateCall != null) { + try { + parent.onAfterTerminateCall.accept(v, null); + } + catch (Throwable t) { + Operators.onErrorDropped(Operators.onOperatorError(t, + actual.currentContext()), + actual.currentContext()); + } + } + } + return v; + } + + @Override + public boolean isEmpty() { + return queueSubscription == null || queueSubscription.isEmpty(); + } + + @Override + public void clear() { + assert queueSubscription != null; + queueSubscription.clear(); + } + + @Override + public int requestFusion(int requestedMode) { + int m; + if (queueSubscription == null) { //source wasn't actually Fuseable + m = NONE; + } + else if ((requestedMode & THREAD_BARRIER) != 0) { + m = NONE; + } + else { + m = queueSubscription.requestFusion(requestedMode); + } + sourceMode = m; + return m; + } + + @Override + public int size() { + return queueSubscription == null ? 0 : queueSubscription.size(); + } + } +} + Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoProcessor.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2016-2021 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.concurrent.CancellationException; +import java.util.stream.Stream; + +import org.reactivestreams.Processor; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A {@code MonoProcessor} is a {@link Processor} that is also a {@link Mono}. + * + *

+ * + * + *

+ * Implementations might implements stateful semantics, allowing multiple subscriptions. + * Once a {@link MonoProcessor} has been resolved, implementations may also replay cached signals to newer subscribers. + *

+ * Despite having default implementations, most methods should be reimplemented with meaningful semantics relevant to + * concrete child classes. + * + * @param the type of the value that will be made available + * + * @author Stephane Maldini + * @deprecated Processors will be removed in 3.5. Prefer using {@link Sinks.One} or {@link Sinks.Empty} instead, + * or see https://github.com/reactor/reactor-core/issues/2431 for alternatives + */ +@Deprecated +public abstract class MonoProcessor extends Mono + implements Processor, CoreSubscriber, Disposable, + Subscription, + Scannable { + + /** + * Create a {@link MonoProcessor} that will eagerly request 1 on {@link #onSubscribe(Subscription)}, cache and emit + * the eventual result for 1 or N subscribers. + * + * @param type of the expected value + * + * @return A {@link MonoProcessor}. + * @deprecated Use {@link Sinks#one()}, to be removed in 3.5 + */ + @Deprecated + public static MonoProcessor create() { + return new NextProcessor<>(null); + } + + /** + * @deprecated the {@link MonoProcessor} will cease to implement {@link Subscription} in 3.5 + */ + @Override + @Deprecated + public void cancel() { + } + + /** + * Indicates whether this {@code MonoProcessor} has been interrupted via cancellation. + * + * @return {@code true} if this {@code MonoProcessor} is cancelled, {@code false} + * otherwise. + * @deprecated the {@link MonoProcessor} will cease to implement {@link Subscription} and this method will be removed in 3.5 + */ + @Deprecated + public boolean isCancelled() { + return false; + } + + /** + * @param n the request amount + * @deprecated the {@link MonoProcessor} will cease to implement {@link Subscription} in 3.5 + */ + @Override + @Deprecated + public void request(long n) { + Operators.validate(n); + } + + @Override + public void dispose() { + onError(new CancellationException("Disposed")); + } + + /** + * Block the calling thread indefinitely, waiting for the completion of this {@code MonoProcessor}. If the + * {@link MonoProcessor} is completed with an error a RuntimeException that wraps the error is thrown. + * + * @return the value of this {@code MonoProcessor} + */ + @Override + @Nullable + public O block() { + return block(null); + } + + /** + * Block the calling thread for the specified time, waiting for the completion of this {@code MonoProcessor}. If the + * {@link MonoProcessor} 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 {@code MonoProcessor} or {@code null} if the timeout is reached and the {@code MonoProcessor} has + * not completed + */ + @Override + @Nullable + public O block(@Nullable Duration timeout) { + return peek(); + } + + /** + * Return the produced {@link Throwable} error if any or null + * + * @return the produced {@link Throwable} error if any or null + */ + @Nullable + public Throwable getError() { + return null; + } + + /** + * Indicates whether this {@code MonoProcessor} has been completed with an error. + * + * @return {@code true} if this {@code MonoProcessor} was completed with an error, {@code false} otherwise. + */ + public final boolean isError() { + return getError() != null; + } + + /** + * Indicates whether this {@code MonoProcessor} has been successfully completed a value. + * + * @return {@code true} if this {@code MonoProcessor} is successful, {@code false} otherwise. + */ + public final boolean isSuccess() { + return isTerminated() && !isError(); + } + + /** + * Indicates whether this {@code MonoProcessor} has been terminated by the + * source producer with a success or an error. + * + * @return {@code true} if this {@code MonoProcessor} is successful, {@code false} otherwise. + */ + public boolean isTerminated() { + return false; + } + + @Override + public boolean isDisposed() { + return isTerminated() || isCancelled(); + } + + /** + * Returns the value that completed this {@link MonoProcessor}. Returns {@code null} if the {@link MonoProcessor} has not been completed. If the + * {@link MonoProcessor} is completed with an error a RuntimeException that wraps the error is thrown. + * + * @return the value that completed the {@link MonoProcessor}, or {@code null} if it has not been completed + * + * @throws RuntimeException if the {@link MonoProcessor} was completed with an error + * @deprecated this method is discouraged, consider peeking into a MonoProcessor by {@link Mono#toFuture() turning it into a CompletableFuture} + */ + @Nullable + @Deprecated + public O peek() { + return null; + } + + @Override + public Context currentContext() { + InnerProducer[] innerProducersArray = + inners().filter(InnerProducer.class::isInstance) + .map(InnerProducer.class::cast) + .toArray(InnerProducer[]::new); + + return Operators.multiSubscribersContext(innerProducersArray); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + //touch guard + boolean t = isTerminated(); + + if (key == Attr.TERMINATED) return t; + if (key == Attr.ERROR) return getError(); + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.CANCELLED) return isCancelled(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + /** + * Return the number of active {@link Subscriber} or {@literal -1} if untracked. + * + * @return the number of active {@link Subscriber} or {@literal -1} if untracked + */ + public long downstreamCount() { + return inners().count(); + } + + /** + * Return true if any {@link Subscriber} is actively subscribed + * + * @return true if any {@link Subscriber} is actively subscribed + */ + public final boolean hasDownstreams() { + return downstreamCount() != 0; + } + + @Override + public Stream inners() { + return Stream.empty(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoPublishMulticast.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoPublishMulticast.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoPublishMulticast.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2017-2021 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.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Shares a {@link Mono} for the duration of a function that may transform it and + * consume it as many times as necessary without causing multiple subscriptions + * to the upstream. + * + * @param the source value type + * @param the output value type + * + * @see Reactive-Streams-Commons + */ +final class MonoPublishMulticast extends InternalMonoOperator implements Fuseable { + + final Function, ? extends Mono> transform; + + MonoPublishMulticast(Mono source, + Function, ? extends Mono> transform) { + super(source); + this.transform = Objects.requireNonNull(transform, "transform"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + MonoPublishMulticaster multicast = new MonoPublishMulticaster<>(actual.currentContext()); + + Mono out = Objects.requireNonNull(transform.apply(fromDirect(multicast)), + "The transform returned a null Mono"); + + if (out instanceof Fuseable) { + out.subscribe(new FluxPublishMulticast.CancelFuseableMulticaster<>(actual, multicast)); + } + else { + out.subscribe(new FluxPublishMulticast.CancelMulticaster<>(actual, multicast)); + } + + return multicast; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class MonoPublishMulticaster extends Mono + implements InnerConsumer, FluxPublishMulticast.PublishMulticasterParent { + + volatile Subscription s; + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(MonoPublishMulticaster.class, + Subscription.class, + "s"); + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(MonoPublishMulticaster.class, "wip"); + + volatile PublishMulticastInner[] subscribers; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater SUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater(MonoPublishMulticaster.class, PublishMulticastInner[].class, "subscribers"); + + @SuppressWarnings("rawtypes") + static final PublishMulticastInner[] EMPTY = new PublishMulticastInner[0]; + + @SuppressWarnings("rawtypes") + static final PublishMulticastInner[] TERMINATED = new PublishMulticastInner[0]; + + volatile boolean done; + + @Nullable + T value; + Throwable error; + + volatile boolean connected; + + final Context context; + + @SuppressWarnings("unchecked") + MonoPublishMulticaster(Context ctx) { + SUBSCRIBERS.lazySet(this, EMPTY); + this.context = ctx; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.ERROR) { + return error; + } + if (key == Attr.CANCELLED) { + return s == Operators.cancelledSubscription(); + } + if (key == Attr.TERMINATED) { + return done; + } + if (key == Attr.PREFETCH) { + return 1; + } + if (key == Attr.BUFFERED) { + return value != null ? 1 : 0; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return null; + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public Context currentContext() { + return context; + } + + @Override + public void subscribe(CoreSubscriber actual) { + PublishMulticastInner pcs = new PublishMulticastInner<>(this, actual); + actual.onSubscribe(pcs); + + if (add(pcs)) { + if (pcs.cancelled == 1) { + remove(pcs); + return; + } + drain(); + } + else { + Throwable ex = error; + if (ex != null) { + actual.onError(ex); + } + else { + actual.onComplete(); + } + } + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + connected = true; + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, context); + return; + } + + value = t; + done = true; + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, context); + return; + } + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + + for (; ; ) { + if (connected) { + if (s == Operators.cancelledSubscription()) { + value = null; + return; + } + + final T v = value; + + PublishMulticastInner[] a = subscribers; + int n = a.length; + + if (n != 0) { + if (s == Operators.cancelledSubscription()) { + value = null; + return; + } + + @SuppressWarnings("unchecked") + PublishMulticastInner[] castedArray = SUBSCRIBERS.getAndSet(this, TERMINATED); + a = castedArray; + n = a.length; + Throwable ex = error; + if (ex != null) { + for (int i = 0; i < n; i++) { + a[i].actual.onError(ex); + } + } + else if (v == null) { + for (int i = 0; i < n; i++) { + a[i].actual.onComplete(); + } + } + else { + for (int i = 0; i < n; i++) { + a[i].actual.onNext(v); + a[i].actual.onComplete(); + } + value = null; + } + return; + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + + boolean add(PublishMulticastInner s) { + for (; ; ) { + PublishMulticastInner[] a = subscribers; + + if (a == TERMINATED) { + return false; + } + + int n = a.length; + + @SuppressWarnings("unchecked") + PublishMulticastInner[] b = new PublishMulticastInner[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = s; + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(PublishMulticastInner s) { + for (; ; ) { + PublishMulticastInner[] 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] == s) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + PublishMulticastInner[] b; + if (n == 1) { + b = EMPTY; + } + else { + b = new PublishMulticastInner[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)) { + return; + } + } + } + + @Override + public void terminate() { + Operators.terminate(S, this); + if (WIP.getAndIncrement(this) == 0) { + if (connected) { + value = null; + } + } + } + } + + static final class PublishMulticastInner implements InnerProducer { + + final MonoPublishMulticaster parent; + + final CoreSubscriber actual; + + volatile int cancelled; + static final AtomicIntegerFieldUpdater CANCELLED = AtomicIntegerFieldUpdater.newUpdater(PublishMulticastInner.class, "cancelled"); + + PublishMulticastInner(MonoPublishMulticaster parent, + CoreSubscriber actual) { + this.parent = parent; + this.actual = actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return parent; + } + if (key == Attr.CANCELLED) { + return cancelled == 1; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + parent.drain(); + } + } + + @Override + public void cancel() { + if (CANCELLED.compareAndSet(this, 0, 1)) { + parent.remove(this); + parent.drain(); + } + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoPublishOn.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoPublishOn.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoPublishOn.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Scannable; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +/** + * Schedules the emission of the value or completion of the wrapped Mono via + * the given Scheduler. + * + * @param the value type + */ +final class MonoPublishOn extends InternalMonoOperator { + + final Scheduler scheduler; + + MonoPublishOn(Mono source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new PublishOnSubscriber(actual, scheduler); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return super.scanUnsafe(key); + } + + static final class PublishOnSubscriber + implements InnerOperator, Runnable { + + final CoreSubscriber actual; + + final Scheduler scheduler; + + Subscription s; + + volatile Disposable future; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + FUTURE = + AtomicReferenceFieldUpdater.newUpdater(PublishOnSubscriber.class, + Disposable.class, + "future"); + + + volatile T value; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + VALUE = + AtomicReferenceFieldUpdater.newUpdater(PublishOnSubscriber.class, + Object.class, + "value"); + + volatile Throwable error; + + PublishOnSubscriber(CoreSubscriber actual, + Scheduler scheduler) { + this.actual = actual; + this.scheduler = scheduler; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return future == OperatorDisposables.DISPOSED; + if (key == Attr.PARENT) return s; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + actual.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + value = t; + trySchedule(this, null, t); + } + + @Override + public void onError(Throwable t) { + error = t; + trySchedule(null, t, null); + } + + @Override + public void onComplete() { + if (value == null) { + trySchedule(null, null, null); + } + } + + void trySchedule( + @Nullable Subscription subscription, + @Nullable Throwable suppressed, + @Nullable Object dataSignal) { + + if(future != null){ + return; + } + + try { + future = this.scheduler.schedule(this); + } + catch (RejectedExecutionException ree) { + actual.onError(Operators.onRejectedExecution(ree, subscription, + suppressed, dataSignal, actual.currentContext())); + } + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + Disposable c = future; + if (c != OperatorDisposables.DISPOSED) { + c = FUTURE.getAndSet(this, OperatorDisposables.DISPOSED); + if (c != null && !OperatorDisposables.isDisposed(c)) { + c.dispose(); + } + value = null; + } + s.cancel(); + } + + @Override + @SuppressWarnings("unchecked") + public void run() { + if (OperatorDisposables.isDisposed(future)) { + return; + } + T v = (T)VALUE.getAndSet(this, null); + + if (v != null) { + actual.onNext(v); + actual.onComplete(); + } + else { + Throwable e = error; + if (e != null) { + actual.onError(e); + } + else { + actual.onComplete(); + } + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoReduce.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoReduce.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoReduce.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2016-2021 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.BiFunction; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Aggregates the source items with an aggregator function and returns the last result. + * + * @param the input and output value type + * + * @see Reactive-Streams-Commons + */ +final class MonoReduce extends MonoFromFluxOperator + implements Fuseable { + + final BiFunction aggregator; + + MonoReduce(Flux source, BiFunction aggregator) { + super(source); + this.aggregator = Objects.requireNonNull(aggregator, "aggregator"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new ReduceSubscriber<>(actual, aggregator); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ReduceSubscriber extends Operators.MonoSubscriber { + + final BiFunction aggregator; + + Subscription s; + + boolean done; + + ReduceSubscriber(CoreSubscriber actual, + BiFunction aggregator) { + super(actual); + this.aggregator = aggregator; + } + + @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 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; + } + T r = this.value; + if (r == null) { + setValue(t); + } + else { + try { + r = Objects.requireNonNull(aggregator.apply(r, t), + "The aggregator returned a null value"); + } + catch (Throwable ex) { + done = true; + Context ctx = actual.currentContext(); + Operators.onDiscard(t, ctx); + Operators.onDiscard(this.value, ctx); + this.value = null; + actual.onError(Operators.onOperatorError(s, ex, t, + actual.currentContext())); + return; + } + + setValue(r); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + discard(this.value); + this.value = null; + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + T r = this.value; + if (r != null) { + complete(r); + } + else { + actual.onComplete(); + } + } + + @Override + public void cancel() { + super.cancel(); + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoReduceSeed.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoReduceSeed.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoReduceSeed.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2016-2021 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.BiFunction; +import java.util.function.Supplier; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Aggregates the source values with the help of an accumulator + * function and emits the final accumulated value. + * + * @param the source value type + * @param the accumulated result type + * + * @see Reactive-Streams-Commons + */ +final class MonoReduceSeed extends MonoFromFluxOperator + implements Fuseable { + + final Supplier initialSupplier; + + final BiFunction accumulator; + + MonoReduceSeed(Flux source, + Supplier initialSupplier, + BiFunction accumulator) { + super(source); + this.initialSupplier = Objects.requireNonNull(initialSupplier, "initialSupplier"); + this.accumulator = Objects.requireNonNull(accumulator, "accumulator"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + R initialValue = Objects.requireNonNull(initialSupplier.get(), + "The initial value supplied is null"); + + return new ReduceSeedSubscriber<>(actual, accumulator, initialValue); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class ReduceSeedSubscriber extends Operators.MonoSubscriber { + + final BiFunction accumulator; + + Subscription s; + + boolean done; + + ReduceSeedSubscriber(CoreSubscriber actual, + BiFunction accumulator, + R value) { + super(actual); + this.accumulator = accumulator; + //noinspection deprecation + this.value = value; //setValue is made NO-OP in order to ignore redundant writes in base class + } + + @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 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. + } + + @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) { + R v = this.value; + R accumulated; + + if (v != null) { //value null when cancelled + try { + accumulated = Objects.requireNonNull(accumulator.apply(v, t), + "The accumulator returned a null value"); + + } + 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 { + Operators.onDiscard(t, actual.currentContext()); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + discard(this.value); + this.value = null; + + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + complete(this.value); + //we DON'T null out the value, complete will do that once there's been a request + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoRepeat.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoRepeat.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoRepeat.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Repeatedly subscribes to the source and relays its values either + * indefinitely or a fixed number of times. + *

+ * The times == Long.MAX_VALUE is treated as infinite repeat. Times = 1 mirrors the source + * (the "original" run) and then repeats it once more. Callers should take care of times = + * 0 and times = -1 cases, which should map to replaying the source and an empty sequence, + * respectively. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoRepeat extends FluxFromMonoOperator { + + final long times; + + MonoRepeat(Mono source, long times) { + super(source); + if (times <= 0L) { + throw new IllegalArgumentException("times > 0 required"); + } + this.times = times; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + FluxRepeat.RepeatSubscriber parent = + new FluxRepeat.RepeatSubscriber<>(source, actual, times + 1); + + actual.onSubscribe(parent); + + if (!parent.isCancelled()) { + parent.onComplete(); + } + return null; + } + + @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/MonoRepeatPredicate.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoRepeatPredicate.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoRepeatPredicate.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016-2021 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.BooleanSupplier; + +import reactor.core.CoreSubscriber; + +/** + * Repeatedly subscribes to the source if the predicate returns true after + * completion of the previous subscription. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoRepeatPredicate extends FluxFromMonoOperator { + + final BooleanSupplier predicate; + + MonoRepeatPredicate(Mono source, BooleanSupplier predicate) { + super(source); + this.predicate = Objects.requireNonNull(predicate, "predicate"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + FluxRepeatPredicate.RepeatPredicateSubscriber parent = new FluxRepeatPredicate.RepeatPredicateSubscriber<>(source, + actual, predicate); + + actual.onSubscribe(parent); + + if (!parent.isCancelled()) { + parent.resubscribe(); + } + return null; + } + + @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/MonoRepeatWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoRepeatWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoRepeatWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; + +/** + * Repeats a source when a companion sequence signals an item in response to the main's + * completion signal + *

+ *

If the companion sequence signals when the main source is active, the repeat attempt + * is suppressed and any terminal signal will terminate the main source with the same + * signal immediately. + * + * @param the source value type + * + * @see Reactive-Streams-Commons + */ +final class MonoRepeatWhen extends FluxFromMonoOperator { + + final Function, ? extends Publisher> whenSourceFactory; + + MonoRepeatWhen(Mono source, + Function, ? extends Publisher> whenSourceFactory) { + super(source); + this.whenSourceFactory = + Objects.requireNonNull(whenSourceFactory, "whenSourceFactory"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + FluxRepeatWhen.RepeatWhenOtherSubscriber other = + new FluxRepeatWhen.RepeatWhenOtherSubscriber(); + + CoreSubscriber serial = Operators.serialize(actual); + + FluxRepeatWhen.RepeatWhenMainSubscriber main = + new FluxRepeatWhen.RepeatWhenMainSubscriber<>(serial, other.completionSignal, source); + other.main = main; + + serial.onSubscribe(main); + + Publisher p; + + try { + p = Objects.requireNonNull(whenSourceFactory.apply(other), + "The whenSourceFactory returned a null Publisher"); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + return null; + } + + p.subscribe(other); + + if (!main.cancelled) { + return main; + } + else { + return null; + } + } + + @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/MonoRetry.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoRetry.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoRetry.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Repeatedly subscribes to the source sequence if it signals any error + * either indefinitely or a fixed number of times. + *

+ * The times == Long.MAX_VALUE is treated as infinite retry. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoRetry extends InternalMonoOperator { + + final long times; + + MonoRetry(Mono source, long times) { + super(source); + if (times < 0L) { + throw new IllegalArgumentException("times >= 0 required"); + } + this.times = times; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + FluxRetry.RetrySubscriber parent = new FluxRetry.RetrySubscriber<>(source, + actual, times); + + actual.onSubscribe(parent); + + if (!parent.isCancelled()) { + parent.resubscribe(); + } + return null; + } + + @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/MonoRetryWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoRetryWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoRetryWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.util.retry.Retry; + +/** + * retries a source when a companion sequence signals an item in response to the main's + * error signal + *

+ *

If the companion sequence signals when the main source is active, the repeat attempt + * is suppressed and any terminal signal will terminate the main source with the same + * signal immediately. + * + * @param the source value type + * @see Reactive-Streams-Commons + */ +final class MonoRetryWhen extends InternalMonoOperator { + + final Retry whenSourceFactory; + + MonoRetryWhen(Mono source, Retry whenSourceFactory) { + super(source); + this.whenSourceFactory = Objects.requireNonNull(whenSourceFactory, "whenSourceFactory"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + FluxRetryWhen.subscribe(actual, whenSourceFactory, source); + return null; + } + + @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/MonoRunnable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoRunnable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoRunnable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015-2021 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.Callable; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * Executes the runnable whenever a Subscriber subscribes to this Mono. + */ +final class MonoRunnable extends Mono implements Callable, SourceProducer { + + final Runnable run; + + MonoRunnable(Runnable run) { + this.run = Objects.requireNonNull(run, "run"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + MonoRunnableEagerSubscription s = new MonoRunnableEagerSubscription(); + actual.onSubscribe(s); + if (s.isCancelled()) { + return; + } + + try { + run.run(); + actual.onComplete(); + } catch (Throwable ex) { + actual.onError(Operators.onOperatorError(ex, actual.currentContext())); + } + } + + @Override + @Nullable + public T block(Duration m) { + run.run(); + return null; + } + + @Override + @Nullable + public T block() { + run.run(); + return null; + } + + @Override + @Nullable + public Void call() throws Exception { + run.run(); + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class MonoRunnableEagerSubscription extends AtomicBoolean implements Subscription { + + @Override + public void request(long n) { + //NO-OP + } + + @Override + public void cancel() { + set(true); + } + + public boolean isCancelled() { + return get(); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSequenceEqual.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSequenceEqual.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSequenceEqual.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiPredicate; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +import static reactor.core.publisher.Operators.cancelledSubscription; + +final class MonoSequenceEqual extends Mono implements SourceProducer { + final Publisher first; + final Publisher second; + final BiPredicate comparer; + final int prefetch; + + MonoSequenceEqual(Publisher first, Publisher second, + BiPredicate comparer, int prefetch) { + this.first = Objects.requireNonNull(first, "first"); + this.second = Objects.requireNonNull(second, "second"); + this.comparer = Objects.requireNonNull(comparer, "comparer"); + if(prefetch < 1){ + throw new IllegalArgumentException("Buffer size must be strictly positive: " + + ""+ prefetch); + } + this.prefetch = prefetch; + } + + @Override + public void subscribe(CoreSubscriber actual) { + EqualCoordinator ec = new EqualCoordinator<>(actual, + prefetch, first, second, comparer); + actual.onSubscribe(ec); + ec.subscribe(); + } + + @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 EqualCoordinator implements InnerProducer { + final CoreSubscriber actual; + final BiPredicate comparer; + final Publisher first; + final Publisher second; + final EqualSubscriber firstSubscriber; + final EqualSubscriber secondSubscriber; + + volatile boolean cancelled; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(EqualCoordinator.class, "once"); + + T v1; + + T v2; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(EqualCoordinator.class, "wip"); + + EqualCoordinator(CoreSubscriber actual, int prefetch, + Publisher first, Publisher second, + BiPredicate comparer) { + this.actual = actual; + this.first = first; + this.second = second; + this.comparer = comparer; + firstSubscriber = new EqualSubscriber<>(this, prefetch); + secondSubscriber = new EqualSubscriber<>(this, prefetch); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(firstSubscriber, secondSubscriber); + } + + void subscribe() { + if (ONCE.compareAndSet(this, 0, 1)) { + first.subscribe(firstSubscriber); + second.subscribe(secondSubscriber); + } + } + + @Override + public void request(long n) { + if (!Operators.validate(n)) { + return; + } + if (ONCE.compareAndSet(this, 0, 1)) { + first.subscribe(firstSubscriber); + second.subscribe(secondSubscriber); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + cancelInner(firstSubscriber); + cancelInner(secondSubscriber); + + if (WIP.getAndIncrement(this) == 0) { + firstSubscriber.queue.clear(); + secondSubscriber.queue.clear(); + } + } + } + + void cancel(EqualSubscriber s1, Queue q1, EqualSubscriber s2, Queue q2) { + cancelled = true; + cancelInner(s1); + q1.clear(); + cancelInner(s2); + q2.clear(); + } + + void cancelInner(EqualSubscriber innerSubscriber) { + Subscription s = innerSubscriber.subscription; + if (s != cancelledSubscription()) { + s = EqualSubscriber.S.getAndSet(innerSubscriber, + cancelledSubscription()); + if (s != null && s != cancelledSubscription()) { + s.cancel(); + } + } + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + final EqualSubscriber s1 = firstSubscriber; + final Queue q1 = s1.queue; + final EqualSubscriber s2 = secondSubscriber; + final Queue q2 = s2.queue; + + for (;;) { + + long r = 0L; + for (;;) { + if (cancelled) { + q1.clear(); + q2.clear(); + return; + } + + boolean d1 = s1.done; + + if (d1) { + Throwable e = s1.error; + if (e != null) { + cancel(s1, q1, s2, q2); + + actual.onError(e); + return; + } + } + + boolean d2 = s2.done; + + if (d2) { + Throwable e = s2.error; + if (e != null) { + cancel(s1, q1, s2, q2); + + actual.onError(e); + return; + } + } + + if (v1 == null) { + v1 = q1.poll(); + } + boolean e1 = v1 == null; + + if (v2 == null) { + v2 = q2.poll(); + } + boolean e2 = v2 == null; + + if (d1 && d2 && e1 && e2) { + actual.onNext(true); + actual.onComplete(); + return; + } + if ((d1 && d2) && (e1 != e2)) { + cancel(s1, q1, s2, q2); + + actual.onNext(false); + actual.onComplete(); + return; + } + + if (!e1 && !e2) { + boolean c; + + try { + c = comparer.test(v1, v2); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + cancel(s1, q1, s2, q2); + + actual.onError(Operators.onOperatorError(ex, + actual.currentContext())); + return; + } + + if (!c) { + cancel(s1, q1, s2, q2); + + actual.onNext(false); + actual.onComplete(); + return; + } + r++; + + v1 = null; + v2 = null; + } + + if (e1 || e2) { + break; + } + } + + if (r != 0L) { + s1.cachedSubscription.request(r); + s2.cachedSubscription.request(r); + } + + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + } + } + + static final class EqualSubscriber + implements InnerConsumer { + final EqualCoordinator parent; + final Queue queue; + final int prefetch; + + volatile boolean done; + Throwable error; + + Subscription cachedSubscription; + volatile Subscription subscription; + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(EqualSubscriber.class, + Subscription.class, "subscription"); + + EqualSubscriber(EqualCoordinator parent, int prefetch) { + this.parent = parent; + this.prefetch = prefetch; + this.queue = Queues.get(prefetch).get(); + } + + @Override + public Context currentContext() { + return parent.actual.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.ERROR) return error; + if (key == Attr.CANCELLED) return subscription == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return subscription; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.BUFFERED) return queue.size(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + this.cachedSubscription = s; + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + if (!queue.offer(t)) { + onError(Operators.onOperatorError(cachedSubscription, Exceptions + .failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t, + currentContext())); + return; + } + parent.drain(); + } + + @Override + public void onError(Throwable t) { + error = t; + done = true; + parent.drain(); + } + + @Override + public void onComplete() { + done = true; + parent.drain(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSingle.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSingle.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSingle.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2016-2021 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.NoSuchElementException; +import java.util.Objects; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Expects and emits a single item from the source or signals + * NoSuchElementException (or a default generated value) for empty source, + * IndexOutOfBoundsException for a multi-item source. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoSingle extends MonoFromFluxOperator { + + final T defaultValue; + final boolean completeOnEmpty; + + MonoSingle(Flux source) { + super(source); + this.defaultValue = null; + this.completeOnEmpty = false; + } + + MonoSingle(Flux source, + @Nullable T defaultValue, + boolean completeOnEmpty) { + super(source); + this.defaultValue = completeOnEmpty ? defaultValue : + Objects.requireNonNull(defaultValue, "defaultValue"); + this.completeOnEmpty = completeOnEmpty; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new SingleSubscriber<>(actual, defaultValue, completeOnEmpty); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class SingleSubscriber extends Operators.MonoInnerProducerBase implements InnerConsumer { + + @Nullable + final T defaultValue; + final boolean completeOnEmpty; + + Subscription s; + + int count; + + 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(); + } + + SingleSubscriber(CoreSubscriber actual, + @Nullable T defaultValue, + boolean completeOnEmpty) { + super(actual); + this.defaultValue = defaultValue; + this.completeOnEmpty = completeOnEmpty; + } + + @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 (isCancelled()) { + //this helps differentiating a duplicate malformed signal "done" from a count > 1 "done" + discard(t); + return; + } + if (done) { + Operators.onNextDropped(t, actual().currentContext()); + return; + } + if (++count > 1) { + discard(t); + //mark as both cancelled and done + cancel(); + onError(new IndexOutOfBoundsException("Source emitted more than one item")); + } + else { + setValue(t); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual().currentContext()); + return; + } + done = true; + discardTheValue(); + + actual().onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + int c = count; + if (c == 0) { + + if (completeOnEmpty) { + actual().onComplete(); + return; + } + + T t = defaultValue; + + if (t != null) { + complete(t); + } + else { + actual().onError(Operators.onOperatorError(this, + new NoSuchElementException("Source was empty"), + actual().currentContext())); + } + } + else if (c == 1) { + complete(); + } + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleCallable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleCallable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleCallable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016-2021 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.NoSuchElementException; +import java.util.Objects; +import java.util.concurrent.Callable; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.util.annotation.Nullable; + +/** + * Expects and emits a single item from the source Callable or signals + * NoSuchElementException for empty source. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoSingleCallable extends Mono + implements Callable, SourceProducer { + + final Callable callable; + @Nullable + final T defaultValue; + + MonoSingleCallable(Callable source) { + this.callable = Objects.requireNonNull(source, "source"); + this.defaultValue = null; + } + + MonoSingleCallable(Callable source, T defaultValue) { + this.callable = Objects.requireNonNull(source, "source"); + this.defaultValue = Objects.requireNonNull(defaultValue, "defaultValue"); + } + + @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(); + if (t == null && defaultValue == null) { + actual.onError(new NoSuchElementException("Source was empty")); + } + else if (t == null) { + sds.complete(defaultValue); + } + else { + sds.complete(t); + } + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + } + + } + + @Override + public T block() { + //duration is ignored below + return block(Duration.ZERO); + } + + @Override + public T block(Duration m) { + final T v; + + try { + v = callable.call(); + } + catch (Throwable e) { + throw Exceptions.propagate(e); + } + + if (v == null && this.defaultValue == null) { + throw new NoSuchElementException("Source was empty"); + } + else if (v == null) { + return this.defaultValue; + } + + return v; + } + + @Override + public T call() throws Exception { + final T v = callable.call(); + + if (v == null && this.defaultValue == null) { + throw new NoSuchElementException("Source was empty"); + } + else if (v == null) { + return this.defaultValue; + } + + return 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/MonoSingleMono.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleMono.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSingleMono.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * Expects and emits a single item from the source Mono or signals + * NoSuchElementException(or a default generated value) for empty source. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoSingleMono extends InternalMonoOperator { + + MonoSingleMono(Mono source) { + super(source); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new MonoSingle.SingleSubscriber<>(actual, null, 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/MonoSink.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSink.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSink.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2015-2021 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.Consumer; +import java.util.function.Function; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Wrapper API around an actual downstream Subscriber + * for emitting nothing, a single value or an error (mutually exclusive). + * + * @param the value type emitted + */ +public interface MonoSink { + + /** + * Complete without any value.

Calling this method multiple times or after the + * other terminating methods has no effect. + */ + void success(); + + /** + * Complete this {@link Mono} with the given value. + *

Calling this method multiple times or after the other + * terminating methods has no effect (the value is {@link Operators#onNextDropped(Object, Context) dropped}). + * Calling this method with a {@code null} value will be silently accepted as a call to + * {@link #success()} by standard implementations. + * + * @param value the value to complete with + */ + void success(@Nullable T value); + + /** + * Terminate with the given exception + *

Calling this method multiple times or after the other terminating methods is + * an unsupported operation. It will discard the exception through the + * {@link Hooks#onErrorDropped(Consumer)} hook. This is to avoid + * complete and silent swallowing of the exception. + * @param e the exception to complete with + */ + void error(Throwable e); + + /** + * Return the current subscriber {@link Context}. + *

+ * {@link Context} can be enriched via {@link Mono#contextWrite(Function)} + * operator or directly by a child subscriber overriding + * {@link CoreSubscriber#currentContext()} + * + * @return the current subscriber {@link Context}. + */ + Context currentContext(); + + /** + * Attaches a {@link LongConsumer} to this {@link MonoSink} that will be notified of + * any request to this sink. + * + * @param consumer the consumer to invoke on request + * + * @return {@link MonoSink} with a consumer that is notified of requests + */ + MonoSink onRequest(LongConsumer consumer); + + /** + * Attach a {@link Disposable} as a callback for when this {@link MonoSink} is + * cancelled. At most one callback can be registered, and subsequent calls to this method + * will result in the immediate disposal of the extraneous {@link Disposable}. + *

+ * The callback is only relevant when the downstream {@link Subscription} is {@link Subscription#cancel() cancelled}. + * + * @param d the {@link Disposable} to use as a callback + * @return the {@link MonoSink} with a cancellation callback + * @see #onDispose(Disposable) onDispose(Disposable) for a callback that covers cancellation AND terminal signals + */ + MonoSink onCancel(Disposable d); + + /** + * Attach a {@link Disposable} as a callback for when this {@link MonoSink} is effectively + * disposed, that is it cannot be used anymore. This includes both having played terminal + * signals (onComplete, onError) and having been cancelled (see {@link #onCancel(Disposable)}). + * At most one callback can be registered, and subsequent calls to this method will result in + * the immediate disposal of the extraneous {@link Disposable}. + *

+ * Note that the "dispose" term is used from the perspective of the sink. Not to + * be confused with {@link Mono#subscribe()}'s {@link Disposable#dispose()} method, which + * maps to disposing the {@link Subscription} (effectively, a {@link Subscription#cancel()} + * signal). + * + * @param d the {@link Disposable} to use as a callback + * @return the {@link MonoSink} with a callback invoked on any terminal signal or on cancellation + * @see #onCancel(Disposable) onCancel(Disposable) for a cancellation-only callback + */ + MonoSink onDispose(Disposable d); +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSource.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSource.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSource.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +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. : + * {@code + * flux.as(Mono::fromDirect) + * .then(d -> Mono.delay(Duration.ofSeconds(1)) + * .block(); + * } + * @param delegate {@link Publisher} type + */ +final class MonoSource extends Mono implements Scannable, SourceProducer, + OptimizableOperator { + + final Publisher source; + + @Nullable + final OptimizableOperator optimizableOperator; + + MonoSource(Publisher source) { + this.source = Objects.requireNonNull(source); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + /** + * Default is simply delegating and decorating with {@link Mono} API. Note this + * assumes an identity between input and output types. + * @param actual + */ + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + source.subscribe(actual); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public final CorePublisher source() { + return this; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return source; + } + if (key == Attr.RUN_STYLE) { + return Scannable.from(source).scanUnsafe(key); + } + return null; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSourceFlux.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSourceFlux.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSourceFlux.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016-2021 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.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; + +/** + * A connecting {@link Mono} Publisher (right-to-left from a composition chain + * perspective) + * + * @param Upstream type + */ +final class MonoSourceFlux extends MonoFromFluxOperator { + + + /** + * Build a {@link MonoSourceFlux} wrapper around the passed parent {@link Publisher} + * + * @param source the {@link Publisher} to decorate + */ + MonoSourceFlux(Flux source) { + super(source); + } + + /** + * Default is simply delegating and decorating with {@link Flux} API. Note this + * assumes an identity between input and output types. + * @param actual + */ + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) { + return Scannable.from(source).scanUnsafe(key); + } + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSourceFluxFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSourceFluxFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSourceFluxFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016-2021 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.Scannable; + +/** + * @author Stephane Maldini + */ +final class MonoSourceFluxFuseable extends MonoFromFluxOperator implements Fuseable { + + MonoSourceFluxFuseable(Flux source) { + super(source); + } + + /** + * Default is simply delegating and decorating with {@link Flux} API. Note this + * assumes an identity between input and output types. + * @param actual + */ + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) { + return Scannable.from(source).scanUnsafe(key); + } + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSourceFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSourceFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSourceFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * @author Stephane Maldini + */ +final class MonoSourceFuseable extends Mono implements Fuseable, Scannable, + OptimizableOperator { + + final Publisher source; + + @Nullable + final OptimizableOperator optimizableOperator; + + MonoSourceFuseable(Publisher source) { + this.source = Objects.requireNonNull(source); + if (source instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) source; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + /** + * Default is simply delegating and decorating with {@link Mono} API. Note this + * assumes an identity between input and output types. + * @param actual + */ + @Override + public void subscribe(CoreSubscriber actual) { + source.subscribe(actual); + } + + @Override + public final CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public final CorePublisher source() { + return this; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return source; + } + if (key == Attr.RUN_STYLE) { + return Scannable.from(source).scanUnsafe(key); + } + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoStreamCollector.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoStreamCollector.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoStreamCollector.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2016-2021 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.Collection; +import java.util.Objects; +import java.util.function.BiConsumer; +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} + * instance. + * + * @param the source value type + * @param an intermediate value type + * @param the output value type + * + * @see Reactive-Streams-Commons + */ +final class MonoStreamCollector extends MonoFromFluxOperator + implements Fuseable { + + final Collector collector; + + MonoStreamCollector(Flux source, + Collector collector) { + super(source); + this.collector = Objects.requireNonNull(collector, "collector"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + A container = collector.supplier() + .get(); + + BiConsumer accumulator = collector.accumulator(); + + Function finisher = collector.finisher(); + + return new StreamCollectorSubscriber<>(actual, container, accumulator, finisher); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class StreamCollectorSubscriber + extends Operators.MonoSubscriber { + + 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, + A container, + BiConsumer accumulator, + Function finisher) { + super(actual); + this.container = container; + this.accumulator = accumulator; + this.finisher = finisher; + } + + @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); + } + + protected void discardIntermediateContainer(A a) { + Context ctx = actual.currentContext(); + if (a instanceof Collection) { + Operators.onDiscardMultiple((Collection) a, ctx); + } + else { + 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); + } + catch (Throwable ex) { + Context ctx = actual.currentContext(); + Operators.onDiscard(t, ctx); + onError(Operators.onOperatorError(s, ex, t, ctx)); //discards intermediate container + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + discardIntermediateContainer(container); + container = null; + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + A a = container; + container = null; + + R r; + + try { + r = finisher.apply(a); + } + catch (Throwable ex) { + discardIntermediateContainer(a); + actual.onError(Operators.onOperatorError(ex, actual.currentContext())); + return; + } + + if (r == null) { + actual.onError(Operators.onOperatorError(new NullPointerException("Collector returned null"), actual.currentContext())); + return; + } + complete(r); + } + + @Override + public void cancel() { + super.cancel(); + s.cancel(); + discardIntermediateContainer(container); + container = null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOn.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOn.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOn.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Scheduler.Worker; +import reactor.util.annotation.Nullable; + +/** + * Subscribes to the upstream Mono on the specified Scheduler and makes sure + * any request from downstream is issued on the same worker where the subscription + * happened. + * + * @param the value type + */ +final class MonoSubscribeOn extends InternalMonoOperator { + + final Scheduler scheduler; + + MonoSubscribeOn(Mono source, Scheduler scheduler) { + super(source); + this.scheduler = scheduler; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + Scheduler.Worker worker = scheduler.createWorker(); + + SubscribeOnSubscriber parent = new SubscribeOnSubscriber<>(source, + actual, worker); + actual.onSubscribe(parent); + + try { + worker.schedule(parent); + } + catch (RejectedExecutionException ree) { + if (parent.s != Operators.cancelledSubscription()) { + actual.onError(Operators.onRejectedExecution(ree, parent, null, null, + actual.currentContext())); + } + } + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return super.scanUnsafe(key); + } + + static final class SubscribeOnSubscriber + implements InnerOperator, Runnable { + + final CoreSubscriber actual; + + final Publisher parent; + + final Scheduler.Worker worker; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(SubscribeOnSubscriber.class, + Subscription.class, + "s"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(SubscribeOnSubscriber.class, + "requested"); + + volatile Thread thread; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater THREAD = + AtomicReferenceFieldUpdater.newUpdater(SubscribeOnSubscriber.class, + Thread.class, + "thread"); + + SubscribeOnSubscriber(Publisher parent, + CoreSubscriber actual, + Worker worker) { + this.actual = actual; + this.parent = parent; + this.worker = worker; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return s; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.RUN_ON) return worker; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void run() { + THREAD.lazySet(this, Thread.currentThread()); + parent.subscribe(this); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + long r = REQUESTED.getAndSet(this, 0L); + if (r != 0L) { + trySchedule(r, s); + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + try{ + actual.onError(t); + } + finally { + worker.dispose(); + THREAD.lazySet(this,null); + } + } + + @Override + public void onComplete() { + actual.onComplete(); + worker.dispose(); + THREAD.lazySet(this,null); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Subscription a = s; + if (a != null) { + trySchedule(n, a); + } + else { + Operators.addCap(REQUESTED, this, n); + a = s; + if (a != null) { + long r = REQUESTED.getAndSet(this, 0L); + if (r != 0L) { + trySchedule(n, a); + } + } + } + } + } + + void trySchedule(long n, Subscription s) { + if (Thread.currentThread() == THREAD.get(this)) { + s.request(n); + } + else { + try { + worker.schedule(() -> s.request(n)); + + } + catch (RejectedExecutionException ree) { + if (!worker.isDisposed()) { + actual.onError(Operators.onRejectedExecution(ree, + this, + null, + null, + actual.currentContext())); + } + } + } + } + + @Override + public void cancel() { + Operators.terminate(S, this); + worker.dispose(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOnCallable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOnCallable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOnCallable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.RejectedExecutionException; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.scheduler.Scheduler; + +/** + * Executes a Callable and emits its value on the given Scheduler. + * + * @param the value type + * @see https://github.com/reactor/reactive-streams-commons + */ +final class MonoSubscribeOnCallable extends Mono implements Fuseable, Scannable{ + + final Callable callable; + + final Scheduler scheduler; + + MonoSubscribeOnCallable(Callable callable, Scheduler scheduler) { + this.callable = Objects.requireNonNull(callable, "callable"); + this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + FluxSubscribeOnCallable.CallableSubscribeOnSubscription parent = + new FluxSubscribeOnCallable.CallableSubscribeOnSubscription<>(actual, callable, scheduler); + actual.onSubscribe(parent); + + try { + parent.setMainFuture(scheduler.schedule(parent)); + } + catch (RejectedExecutionException ree) { + if(parent.state != FluxSubscribeOnCallable.CallableSubscribeOnSubscription.HAS_CANCELLED) { + actual.onError(Operators.onRejectedExecution(ree, actual.currentContext())); + } + } + } + + @Override + public Object scanUnsafe(Scannable.Attr key) { + if (key == Scannable.Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOnValue.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOnValue.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSubscribeOnValue.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016-2021 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.RejectedExecutionException; + +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.core.publisher.FluxSubscribeOnValue.ScheduledEmpty; +import reactor.core.publisher.FluxSubscribeOnValue.ScheduledScalar; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +/** + * Mono indicating a scalar/empty source that subscribes on the specified scheduler. + * + * @param + */ +final class MonoSubscribeOnValue extends Mono implements Scannable { + + final T value; + + final Scheduler scheduler; + + MonoSubscribeOnValue(@Nullable T value, Scheduler scheduler) { + this.value = value; + this.scheduler = Objects.requireNonNull(scheduler, "scheduler"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + T v = value; + if (v == null) { + ScheduledEmpty parent = new ScheduledEmpty(actual); + actual.onSubscribe(parent); + try { + parent.setFuture(scheduler.schedule(parent)); + } + catch (RejectedExecutionException ree) { + if (parent.future != OperatorDisposables.DISPOSED) { + actual.onError(Operators.onRejectedExecution(ree, + actual.currentContext())); + } + } + } + else { + actual.onSubscribe(new ScheduledScalar<>(actual, v, scheduler)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_ON) return scheduler; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC; + + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSupplier.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSupplier.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSupplier.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.function.Supplier; + + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Executes a Supplier function and emits a single value to each individual Subscriber. + * + * @param the returned value type + * @see Reactive-Streams-Commons + */ +final class MonoSupplier +extends Mono + implements Callable, Fuseable, SourceProducer { + + final Supplier supplier; + + MonoSupplier(Supplier callable) { + this.supplier = Objects.requireNonNull(callable, "callable"); + } + + @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())); + } + } + + @Override + @Nullable + public T block(Duration m) { + return supplier.get(); + } + + @Override + @Nullable + public T block() { + //the duration is ignored above + return block(Duration.ZERO); + } + + @Override + @Nullable + public T call() throws Exception { + return supplier.get(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoSwitchIfEmpty.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoSwitchIfEmpty.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoSwitchIfEmpty.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; + +/** + * Switches to another source if the first source turns out to be empty. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class MonoSwitchIfEmpty extends InternalMonoOperator { + + final Mono other; + + MonoSwitchIfEmpty(Mono source, Mono other) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + FluxSwitchIfEmpty.SwitchIfEmptySubscriber parent = new + FluxSwitchIfEmpty.SwitchIfEmptySubscriber<>(actual, other); + + actual.onSubscribe(parent); + + return parent; + } + + @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/MonoTakeLastOne.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoTakeLastOne.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoTakeLastOne.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2016-2021 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.NoSuchElementException; +import java.util.Objects; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Take the very last value from a Publisher source and and emit that one. + * + * @param the value type + */ +final class MonoTakeLastOne extends MonoFromFluxOperator + implements Fuseable { + + final T defaultValue; + + MonoTakeLastOne(Flux source) { + super(source); + this.defaultValue = null; + } + + MonoTakeLastOne(Flux source, T defaultValue) { + super(source); + this.defaultValue = Objects.requireNonNull(defaultValue, "defaultValue"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new TakeLastOneSubscriber<>(actual, defaultValue, true); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return super.scanUnsafe(key); + } + + static final class TakeLastOneSubscriber + extends Operators.MonoSubscriber { + + final boolean mustEmit; + final T defaultValue; + Subscription s; + + TakeLastOneSubscriber(CoreSubscriber actual, + @Nullable T defaultValue, + boolean mustEmit) { + super(actual); + this.defaultValue = 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; + + return super.scanUnsafe(key); + } + + @Override + public void onNext(T t) { + T old = this.value; + setValue(t); + Operators.onDiscard(old, actual.currentContext()); //FIXME cache context + } + + @Override + public void onComplete() { + T v = this.value; + 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())); + } + } + else { + actual.onComplete(); + } + return; + } + complete(v); + } + + @Override + public void cancel() { + super.cancel(); + s.cancel(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoTakeUntilOther.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoTakeUntilOther.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoTakeUntilOther.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016-2021 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.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.context.Context; + +/** + * @author Simon Baslé + */ +final class MonoTakeUntilOther extends InternalMonoOperator { + + private final Publisher other; + + MonoTakeUntilOther(Mono source, Publisher other) { + super(source); + this.other = Objects.requireNonNull(other, "other"); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + FluxTakeUntilOther.TakeUntilMainSubscriber mainSubscriber = new FluxTakeUntilOther.TakeUntilMainSubscriber<>( + actual); + + FluxTakeUntilOther.TakeUntilOtherSubscriber otherSubscriber = new FluxTakeUntilOther.TakeUntilOtherSubscriber<>(mainSubscriber); + + other.subscribe(otherSubscriber); + return mainSubscriber; + } + + @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/MonoTimed.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoTimed.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoTimed.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016-2021 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.time.Instant; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import org.reactivestreams.Subscription; + +import reactor.core.CoreSubscriber; +import reactor.core.scheduler.Scheduler; +import reactor.util.annotation.Nullable; + +/** + * @author Simon Baslé + */ +final class MonoTimed extends InternalMonoOperator> { + + final Scheduler clock; + + MonoTimed(Mono source, Scheduler clock) { + super(source); + this.clock = clock; + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber> actual) { + return new FluxTimed.TimedSubscriber<>(actual, this.clock); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PREFETCH) return 0; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoTimeout.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoTimeout.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoTimeout.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2016-2021 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 org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; + +import static reactor.core.publisher.FluxTimeout.addNameToTimeoutDescription; + +/** + * Signals a timeout (or switches to another sequence) in case a per-item generated + * Publisher source fires an item or completes before the next item arrives from the main + * source. + * + * @param the main source type + * @param the value type for the timeout for the very first item + * @param the value type for the timeout for the subsequent items + * @see Reactive-Streams-Commons + */ +final class MonoTimeout extends InternalMonoOperator { + + final Publisher firstTimeout; + + final Publisher other; + final String timeoutDescription; //only useful when no `other` + + @SuppressWarnings("rawtypes") + final static Function NEVER = e -> Flux.never(); + + MonoTimeout(Mono source, + Publisher firstTimeout, + String timeoutDescription) { + super(source); + this.firstTimeout = Objects.requireNonNull(firstTimeout, "firstTimeout"); + this.other = null; + this.timeoutDescription = timeoutDescription; + } + + MonoTimeout(Mono source, + Publisher firstTimeout, + Publisher other) { + super(source); + this.firstTimeout = Objects.requireNonNull(firstTimeout, "firstTimeout"); + this.other = Objects.requireNonNull(other, "other"); + this.timeoutDescription = null; + } + + @Override + @SuppressWarnings("unchecked") + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return new FluxTimeout.TimeoutMainSubscriber( + Operators.serialize(actual), + firstTimeout, + NEVER, + other, + addNameToTimeoutDescription(source, timeoutDescription) + ); + } + + @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/MonoToCompletableFuture.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoToCompletableFuture.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoToCompletableFuture.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016-2021 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.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.util.context.Context; + +/** + * @author Stephane Maldini + */ +final class MonoToCompletableFuture extends CompletableFuture implements CoreSubscriber { + + final AtomicReference ref = new AtomicReference<>(); + final boolean cancelSourceOnNext; + + MonoToCompletableFuture(boolean sourceCanEmitMoreThanOnce) { + this.cancelSourceOnNext = sourceCanEmitMoreThanOnce; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + boolean cancelled = super.cancel(mayInterruptIfRunning); + if (cancelled) { + Subscription s = ref.getAndSet(null); + if (s != null) { + s.cancel(); + } + } + return cancelled; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(ref.getAndSet(s), s)) { + s.request(Long.MAX_VALUE); + } + else { + s.cancel(); + } + } + + @Override + public void onNext(T t) { + Subscription s = ref.getAndSet(null); + if (s != null) { + complete(t); + if (cancelSourceOnNext) { + s.cancel(); + } + } + else { + Operators.onNextDropped(t, currentContext()); + } + } + + @Override + public void onError(Throwable t) { + if (ref.getAndSet(null) != null) { + completeExceptionally(t); + } + } + + @Override + public void onComplete() { + if (ref.getAndSet(null) != null) { + complete(null); + } + } + + @Override + public Context currentContext() { + return Context.empty(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoUsing.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoUsing.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoUsing.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2016-2021 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.Callable; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.function.Consumer; +import java.util.function.Function; + +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; + +/** + * Uses a resource, generated by a supplier for each individual Subscriber, + * while streaming the values from a + * Publisher derived from the same resource and makes sure the resource is released + * if the sequence terminates or the Subscriber cancels. + *

+ *

+ * Eager resource cleanup happens just before the source termination and exceptions + * raised by the cleanup Consumer may override the terminal event. Non-eager + * cleanup will drop any exception. + * + * @param the value type streamed + * @param the resource type + * + * @see Reactive-Streams-Commons + */ +final class MonoUsing extends Mono implements Fuseable, SourceProducer { + + final Callable resourceSupplier; + + final Function> sourceFactory; + + final Consumer resourceCleanup; + + final boolean eager; + + MonoUsing(Callable resourceSupplier, + Function> sourceFactory, + Consumer resourceCleanup, + boolean eager) { + this.resourceSupplier = + Objects.requireNonNull(resourceSupplier, "resourceSupplier"); + this.sourceFactory = Objects.requireNonNull(sourceFactory, "sourceFactory"); + this.resourceCleanup = Objects.requireNonNull(resourceCleanup, "resourceCleanup"); + this.eager = eager; + } + + @Override + public void subscribe(CoreSubscriber actual) { + S resource; + + try { + resource = resourceSupplier.call(); + } + catch (Throwable e) { + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + + Mono p; + + try { + p = Objects.requireNonNull(sourceFactory.apply(resource), + "The sourceFactory returned a null value"); + } + catch (Throwable e) { + + try { + resourceCleanup.accept(resource); + } + catch (Throwable ex) { + e = Exceptions.addSuppressed(ex, Operators.onOperatorError(e, actual.currentContext())); + } + + Operators.error(actual, Operators.onOperatorError(e, actual.currentContext())); + return; + } + + if (p instanceof Fuseable) { + p.subscribe(new MonoUsingSubscriber<>(actual, + resourceCleanup, + resource, + eager, + true)); + } + else { + p.subscribe(new MonoUsingSubscriber<>(actual, + resourceCleanup, + resource, + eager, + false)); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class MonoUsingSubscriber + implements InnerOperator, QueueSubscription { + + final CoreSubscriber actual; + + final Consumer resourceCleanup; + + final S resource; + + final boolean eager; + final boolean allowFusion; + + Subscription s; + @Nullable + QueueSubscription qs; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(MonoUsingSubscriber.class, "wip"); + + int mode; + boolean valued; + + MonoUsingSubscriber(CoreSubscriber actual, + Consumer resourceCleanup, + S resource, + boolean eager, + boolean allowFusion) { + this.actual = actual; + this.resourceCleanup = resourceCleanup; + this.resource = resource; + this.eager = eager; + this.allowFusion = allowFusion; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) + return wip == 1; + if (key == Attr.PARENT) return s; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + if (WIP.compareAndSet(this, 0, 1)) { + s.cancel(); + + cleanup(); + } + } + + void cleanup() { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + + @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); + } + } + + @Override + public void onNext(T t) { + if (mode == ASYNC) { + actual.onNext(null); + return; + } + this.valued = true; + + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Context ctx = actual.currentContext(); + actual.onError(Operators.onOperatorError(e, ctx)); + Operators.onDiscard(t, ctx); + return; + } + } + + actual.onNext(t); + actual.onComplete(); + + if (!eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + } + + @Override + public void onError(Throwable t) { + if (valued && mode != ASYNC) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Throwable _e = Operators.onOperatorError(e, actual.currentContext()); + t = Exceptions.addSuppressed(_e, t); + } + } + + actual.onError(t); + + if (!eager && WIP.compareAndSet(this, 0, 1)) { + cleanup(); + } + } + + @Override + public void onComplete() { + if (valued && mode != ASYNC) { + return; + } + //this should only happen in the empty case + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + actual.onError(Operators.onOperatorError(e, actual.currentContext())); + return; + } + } + + actual.onComplete(); + + if (!eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable e) { + Operators.onErrorDropped(e, actual.currentContext()); + } + } + } + + @Override + public void clear() { + if (qs != null) { + qs.clear(); + } + } + + @Override + public boolean isEmpty() { + return qs == null || qs.isEmpty(); + } + + @Override + @Nullable + public T poll() { + if (mode == NONE || qs == null) { + return null; + } + + T v = qs.poll(); + + if (v != null) { + valued = true; + if (eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); //throws upwards + } + catch (Throwable t) { + Operators.onDiscard(v, actual.currentContext()); + throw t; + } + } + } + else if (mode == SYNC) { + if (!eager && WIP.compareAndSet(this, 0, 1)) { + try { + resourceCleanup.accept(resource); + } + catch (Throwable t) { + if (!valued) throw t; + else Operators.onErrorDropped(t, actual.currentContext()); + //returns null, ie onComplete, in the second case + } + } + } + return v; + } + + @Override + public int requestFusion(int requestedMode) { + if (qs == null) { + mode = NONE; + return NONE; + } + int m = qs.requestFusion(requestedMode); + mode = m; + return m; + } + + @Override + public int size() { + if (qs == null) { + return 0; + } + return qs.size(); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoUsingWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoUsingWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoUsingWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2018-2021 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.Callable; +import java.util.function.BiFunction; +import java.util.function.Function; + +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; +import reactor.util.context.Context; + +/** + * Uses a resource, generated by a {@link Publisher} for each individual {@link Subscriber}, + * while streaming the values from a {@link Publisher} derived from the same resource. + * Whenever the resulting sequence terminates, the relevant {@link Function} generates + * a "cleanup" {@link Publisher} that is invoked but doesn't change the content of the + * main sequence. Instead it just defers the termination (unless it errors, in which case + * the error suppresses the original termination signal). + * + * @param the value type streamed + * @param the resource type + */ +final class MonoUsingWhen extends Mono implements SourceProducer { + + final Publisher resourceSupplier; + final Function> resourceClosure; + final Function> asyncComplete; + final BiFunction> asyncError; + @Nullable + final Function> asyncCancel; + + MonoUsingWhen(Publisher resourceSupplier, + Function> resourceClosure, + Function> asyncComplete, + BiFunction> asyncError, + @Nullable Function> asyncCancel) { + this.resourceSupplier = Objects.requireNonNull(resourceSupplier, "resourceSupplier"); + this.resourceClosure = Objects.requireNonNull(resourceClosure, "resourceClosure"); + this.asyncComplete = Objects.requireNonNull(asyncComplete, "asyncComplete"); + this.asyncError = Objects.requireNonNull(asyncError, "asyncError"); + this.asyncCancel = asyncCancel; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber actual) { + if (resourceSupplier instanceof Callable) { + try { + Callable resourceCallable = (Callable) resourceSupplier; + S resource = resourceCallable.call(); + + if (resource == null) { + Operators.complete(actual); + } + else { + final Mono p = deriveMonoFromResource(resource, resourceClosure); + final UsingWhenSubscriber subscriber = prepareSubscriberForResource(resource, + actual, + asyncComplete, + asyncError, + asyncCancel, + null); + + p.subscribe(subscriber); + } + } + catch (Throwable e) { + Operators.error(actual, e); + } + return; + } + + resourceSupplier.subscribe(new ResourceSubscriber(actual, resourceClosure, + asyncComplete, asyncError, asyncCancel, + resourceSupplier instanceof Mono)); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + private static Mono deriveMonoFromResource( + RESOURCE resource, + Function> resourceClosure) { + + Mono p; + + try { + p = Objects.requireNonNull(resourceClosure.apply(resource), + "The resourceClosure function returned a null value"); + } + catch (Throwable e) { + p = Mono.error(e); + } + + return p; + } + + private static MonoUsingWhenSubscriber prepareSubscriberForResource( + RESOURCE resource, + CoreSubscriber actual, + Function> asyncComplete, + BiFunction> asyncError, + @Nullable Function> asyncCancel, + @Nullable DeferredSubscription arbiter) { + //MonoUsingWhen cannot support ConditionalSubscriber as there's no way to defer tryOnNext + return new MonoUsingWhenSubscriber<>(actual, + resource, + asyncComplete, + asyncError, + asyncCancel, + arbiter); + } + + //needed to correctly call prepareSubscriberForResource with Mono.from conversions + static class ResourceSubscriber extends DeferredSubscription implements InnerConsumer { + + final CoreSubscriber actual; + final Function> resourceClosure; + final Function> asyncComplete; + final BiFunction> asyncError; + @Nullable + final Function> asyncCancel; + final boolean isMonoSource; + + Subscription resourceSubscription; + boolean resourceProvided; + + UsingWhenSubscriber closureSubscriber; + + ResourceSubscriber(CoreSubscriber actual, + Function> resourceClosure, + Function> asyncComplete, + BiFunction> asyncError, + @Nullable Function> asyncCancel, + boolean isMonoSource) { + this.actual = Objects.requireNonNull(actual, "actual"); + this.resourceClosure = Objects.requireNonNull(resourceClosure, "resourceClosure"); + this.asyncComplete = Objects.requireNonNull(asyncComplete, "asyncComplete"); + this.asyncError = Objects.requireNonNull(asyncError, "asyncError"); + this.asyncCancel = asyncCancel; + this.isMonoSource = isMonoSource; + } + + @Override + public Context currentContext() { + return actual.currentContext(); + } + + @Override + public void onNext(S resource) { + if (resourceProvided) { + Operators.onNextDropped(resource, actual.currentContext()); + return; + } + resourceProvided = true; + + final Mono p = deriveMonoFromResource(resource, resourceClosure); + + this.closureSubscriber = + prepareSubscriberForResource(resource, + this.actual, + this.asyncComplete, + this.asyncError, + this.asyncCancel, + this); + + p.subscribe(closureSubscriber); + + if (!isMonoSource) { + resourceSubscription.cancel(); + } + } + + @Override + public void onError(Throwable throwable) { + if (resourceProvided) { + Operators.onErrorDropped(throwable, actual.currentContext()); + return; + } + //even if no resource provided, actual.onSubscribe has been called + //let's immediately fail actual + actual.onError(throwable); + } + + @Override + public void onComplete() { + if (resourceProvided) { + return; + } + //even if no resource provided, actual.onSubscribe has been called + //let's immediately complete actual + actual.onComplete(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.resourceSubscription, s)) { + this.resourceSubscription = s; + actual.onSubscribe(this); + s.request(Long.MAX_VALUE); + } + } + + @Override + public void cancel() { + if (!resourceProvided) { + resourceSubscription.cancel(); + super.cancel(); + } + else { + super.terminate(); + if (closureSubscriber != null) { + closureSubscriber.cancel(); + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return resourceSubscription; + if (key == Attr.ACTUAL) return actual; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.TERMINATED) return resourceProvided; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + } + + static class MonoUsingWhenSubscriber extends FluxUsingWhen.UsingWhenSubscriber { + + MonoUsingWhenSubscriber(CoreSubscriber actual, + S resource, + Function> asyncComplete, + BiFunction> asyncError, + @Nullable Function> asyncCancel, + @Nullable DeferredSubscription arbiter) { + super(actual, resource, asyncComplete, asyncError, asyncCancel, arbiter); + } + + T value; + + @Override + public void onNext(T value) { + this.value = value; + } + + @Override + public void deferredComplete() { + this.error = Exceptions.TERMINATED; + if (this.value != null) { + actual.onNext(value); + } + this.actual.onComplete(); + } + + @Override + public void deferredError(Throwable error) { + Operators.onDiscard(this.value, actual.currentContext()); + this.error = error; + this.actual.onError(error); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoWhen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoWhen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoWhen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2017-2021 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.AtomicIntegerFieldUpdater; +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.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Waits for all Mono sources to produce a value or terminate, and if all of them produced + * a value, emit a Tuples of those values; otherwise terminate. + */ +final class MonoWhen extends Mono implements SourceProducer { + + final boolean delayError; + + final Publisher[] sources; + + final Iterable> sourcesIterable; + + MonoWhen(boolean delayError, Publisher... sources) { + this.delayError = delayError; + this.sources = Objects.requireNonNull(sources, "sources"); + this.sourcesIterable = null; + } + + MonoWhen(boolean delayError, Iterable> sourcesIterable) { + this.delayError = delayError; + this.sources = null; + this.sourcesIterable = Objects.requireNonNull(sourcesIterable, "sourcesIterable"); + } + + @SuppressWarnings("unchecked") + @Nullable + Mono whenAdditionalSource(Publisher source) { + Publisher[] oldSources = sources; + if (oldSources != null) { + int oldLen = oldSources.length; + Publisher[] newSources = new Publisher[oldLen + 1]; + System.arraycopy(oldSources, 0, newSources, 0, oldLen); + newSources[oldLen] = source; + + return new MonoWhen(delayError, newSources); + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public void subscribe(CoreSubscriber actual) { + Publisher[] a; + int n = 0; + if (sources != null) { + a = sources; + n = a.length; + } + else { + a = new Publisher[8]; + for (Publisher m : sourcesIterable) { + if (n == a.length) { + Publisher[] b = new Publisher[n + (n >> 2)]; + System.arraycopy(a, 0, b, 0, n); + a = b; + } + a[n++] = m; + } + } + + if (n == 0) { + Operators.complete(actual); + return; + } + + WhenCoordinator parent = new WhenCoordinator(actual, n, delayError); + actual.onSubscribe(parent); + parent.subscribe(a); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + static final class WhenCoordinator extends Operators.MonoSubscriber { + + final WhenInner[] subscribers; + + final boolean delayError; + + volatile int done; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater DONE = + AtomicIntegerFieldUpdater.newUpdater(WhenCoordinator.class, "done"); + + @SuppressWarnings("unchecked") + WhenCoordinator(CoreSubscriber subscriber, + int n, + boolean delayError) { + super(subscriber); + this.delayError = delayError; + subscribers = new WhenInner[n]; + for (int i = 0; i < n; i++) { + subscribers[i] = new WhenInner(this); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) { + return done == 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); + } + + @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]); + } + } + + void signalError(Throwable t) { + if (delayError) { + signal(); + } + 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; + } + + Throwable error = null; + Throwable compositeError = null; + + for (int i = 0; i < a.length; i++) { + WhenInner m = a[i]; + Throwable e = m.error; + if (e != null) { + if (compositeError != null) { + //this is ok as the composite created below is never a singleton + compositeError.addSuppressed(e); + } + else if (error != null) { + compositeError = Exceptions.multiple(error, e); + } + else { + error = e; + } + } + + } + + if (compositeError != null) { + actual.onError(compositeError); + } + else if (error != null) { + actual.onError(error); + } + else { + actual.onComplete(); + } + } + + @Override + public void cancel() { + if (!isCancelled()) { + super.cancel(); + for (WhenInner ms : subscribers) { + ms.cancel(); + } + } + } + } + + static final class WhenInner implements InnerConsumer { + + final WhenCoordinator parent; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(WhenInner.class, + Subscription.class, + "s"); + Throwable error; + + WhenInner(WhenCoordinator parent) { + this.parent = parent; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) { + return s == Operators.cancelledSubscription(); + } + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.ACTUAL) { + return parent; + } + if (key == Attr.ERROR) { + return error; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return null; + } + + @Override + public Context currentContext() { + return parent.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) { + } + + @Override + public void onError(Throwable t) { + error = t; + parent.signalError(t); + } + + @Override + public void onComplete() { + parent.signal(); + } + + void cancel() { + Operators.terminate(S, this); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/MonoZip.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/MonoZip.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/MonoZip.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +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.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Waits for all Mono sources to produce a value or terminate, and if all of them produced + * a value, emit a Tuples of those values; otherwise terminate. + * + * @param the source value types + */ +final class MonoZip extends Mono implements SourceProducer { + + final boolean delayError; + + final Publisher[] sources; + + final Iterable> sourcesIterable; + + final Function zipper; + + @SuppressWarnings("unchecked") + MonoZip(boolean delayError, + Publisher p1, + Publisher p2, + BiFunction zipper2) { + this(delayError, + new FluxZip.PairwiseZipper<>(new BiFunction[]{ + Objects.requireNonNull(zipper2, "zipper2")}), + Objects.requireNonNull(p1, "p1"), + Objects.requireNonNull(p2, "p2")); + } + + MonoZip(boolean delayError, + Function zipper, + Publisher... sources) { + this.delayError = delayError; + this.zipper = Objects.requireNonNull(zipper, "zipper"); + this.sources = Objects.requireNonNull(sources, "sources"); + this.sourcesIterable = null; + } + + MonoZip(boolean delayError, + Function zipper, + Iterable> sourcesIterable) { + this.delayError = delayError; + this.zipper = Objects.requireNonNull(zipper, "zipper"); + this.sources = null; + this.sourcesIterable = Objects.requireNonNull(sourcesIterable, "sourcesIterable"); + } + + @SuppressWarnings("unchecked") + @Nullable + Mono zipAdditionalSource(Publisher source, BiFunction zipper) { + Publisher[] oldSources = sources; + if (oldSources != null && this.zipper instanceof FluxZip.PairwiseZipper) { + int oldLen = oldSources.length; + Publisher[] newSources = new Publisher[oldLen + 1]; + System.arraycopy(oldSources, 0, newSources, 0, oldLen); + newSources[oldLen] = source; + + Function z = + ((FluxZip.PairwiseZipper) this.zipper).then(zipper); + + return new MonoZip<>(delayError, z, newSources); + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public void subscribe(CoreSubscriber actual) { + Publisher[] a; + int n = 0; + if (sources != null) { + a = sources; + n = a.length; + } + else { + a = new Publisher[8]; + for (Publisher m : sourcesIterable) { + if (n == a.length) { + Publisher[] b = new Publisher[n + (n >> 2)]; + System.arraycopy(a, 0, b, 0, n); + a = b; + } + a[n++] = m; + } + } + + if (n == 0) { + Operators.complete(actual); + 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]); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return null; + } + + static final class ZipCoordinator extends Operators.MonoSubscriber { + + final ZipInner[] subscribers; + + final boolean delayError; + + final Function zipper; + + volatile int done; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater DONE = + AtomicIntegerFieldUpdater.newUpdater(ZipCoordinator.class, "done"); + + @SuppressWarnings("unchecked") + ZipCoordinator(CoreSubscriber subscriber, + int n, + boolean delayError, + Function zipper) { + super(subscriber); + this.delayError = delayError; + this.zipper = zipper; + subscribers = new ZipInner[n]; + for (int i = 0; i < n; i++) { + subscribers[i] = new ZipInner<>(this); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) { + return done == 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); + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @SuppressWarnings("unchecked") + void signal() { + ZipInner[] a = subscribers; + int n = a.length; + if (DONE.incrementAndGet(this) != n) { + return; + } + + Object[] o = new Object[n]; + Throwable error = null; + Throwable compositeError = null; + boolean hasEmpty = false; + + for (int i = 0; i < a.length; i++) { + ZipInner m = a[i]; + Object v = m.value; + if (v != null) { + o[i] = v; + } + else { + Throwable e = m.error; + if (e != null) { + if (compositeError != null) { + //this is ok as the composite created below is never a singleton + compositeError.addSuppressed(e); + } + else if (error != null) { + compositeError = Exceptions.multiple(error, e); + } + else { + error = e; + } + } + else { + hasEmpty = true; + } + } + } + + if (compositeError != null) { + actual.onError(compositeError); + } + else if (error != null) { + actual.onError(error); + } + else if (hasEmpty) { + actual.onComplete(); + } + else { + R r; + try { + r = Objects.requireNonNull(zipper.apply(o), + "zipper produced a null value"); + } + catch (Throwable t) { + actual.onError(Operators.onOperatorError(null, + t, + o, + actual.currentContext())); + return; + } + complete(r); + } + } + + @Override + public void cancel() { + if (!isCancelled()) { + super.cancel(); + for (ZipInner ms : subscribers) { + ms.cancel(); + } + } + } + + void cancelExcept(ZipInner source) { + if (!isCancelled()) { + super.cancel(); + for (ZipInner ms : subscribers) { + if(ms != source) { + ms.cancel(); + } + } + } + } + } + + static final class ZipInner implements InnerConsumer { + + final ZipCoordinator parent; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(ZipInner.class, + Subscription.class, + "s"); + + Object value; + Throwable error; + + ZipInner(ZipCoordinator parent) { + this.parent = parent; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) { + return s == Operators.cancelledSubscription(); + } + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.ACTUAL) { + return parent; + } + if (key == Attr.ERROR) { + return error; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + + return null; + } + + @Override + public Context currentContext() { + return parent.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(); + } + } + + @Override + public void onError(Throwable t) { + error = t; + if (parent.delayError) { + parent.signal(); + } + else { + int n = parent.subscribers.length; + if (ZipCoordinator.DONE.getAndSet(parent, n) != n) { + parent.cancelExcept(this); + parent.actual.onError(t); + } + } + } + + @Override + public void onComplete() { + if (value == null) { + if (parent.delayError) { + parent.signal(); + } + else { + int n = parent.subscribers.length; + if (ZipCoordinator.DONE.getAndSet(parent, n) != n) { + parent.cancelExcept(this); + parent.actual.onComplete(); + } + } + } + } + + void cancel() { + Operators.terminate(S, this); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/NextProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/NextProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/NextProcessor.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,544 @@ +/* + * Copyright (c) 2016-2021 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.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.publisher.Sinks.EmissionException; +import reactor.core.publisher.Sinks.EmitFailureHandler; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +// 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 { + + /** + * This boolean indicates a usage as `Mono#share()` where, for alignment with Flux#share(), the removal of all + * subscribers should lead to the cancellation of the upstream Subscription. + */ + final boolean isRefCounted; + + volatile NextInner[] subscribers; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater SUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater(NextProcessor.class, NextInner[].class, "subscribers"); + + @SuppressWarnings("rawtypes") + static final NextInner[] EMPTY = new NextInner[0]; + + @SuppressWarnings("rawtypes") + static final NextInner[] TERMINATED = new NextInner[0]; + + @SuppressWarnings("rawtypes") + static final NextInner[] EMPTY_WITH_SOURCE = new NextInner[0]; + + volatile Subscription subscription; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater UPSTREAM = + AtomicReferenceFieldUpdater.newUpdater(NextProcessor.class, Subscription.class, "subscription"); + + @Nullable + CorePublisher source; + @Nullable + Throwable error; + @Nullable + O value; + + NextProcessor(@Nullable CorePublisher source) { + this(source, false); + } + + NextProcessor(@Nullable CorePublisher source, boolean isRefCounted) { + this.source = source; + this.isRefCounted = isRefCounted; + SUBSCRIBERS.lazySet(this, source != null ? EMPTY_WITH_SOURCE : EMPTY); + } + + @Override + public O peek() { + if (!isTerminated()) { + return null; + } + + if (value != null) { + return value; + } + + if (error != null) { + RuntimeException re = Exceptions.propagate(error); + re = Exceptions.addSuppressed(re, new Exception("Mono#peek terminated with an error")); + throw re; + } + + return null; + } + + @Override + @Nullable + public O block(@Nullable Duration timeout) { + try { + if (isTerminated()) { + return peek(); + } + + connect(); + + long delay; + if (null == timeout) { + delay = 0L; + } + else { + delay = System.nanoTime() + timeout.toNanos(); + } + for (; ; ) { + if (isTerminated()) { + if (error != null) { + RuntimeException re = Exceptions.propagate(error); + re = Exceptions.addSuppressed(re, new Exception("Mono#block terminated with an error")); + throw re; + } + return value; + } + if (timeout != null && delay < System.nanoTime()) { + cancel(); + throw new IllegalStateException("Timeout on Mono blocking read"); + } + + Thread.sleep(1); + } + + } + catch (InterruptedException ie) { + Thread.currentThread() + .interrupt(); + + throw new IllegalStateException("Thread Interruption on Mono blocking read"); + } + } + + @Override + public final void onComplete() { + //no particular error condition handling for onComplete + @SuppressWarnings("unused") EmitResult emitResult = tryEmitValue(null); + } + + void emitEmpty(Sinks.EmitFailureHandler failureHandler) { + for (;;) { + Sinks.EmitResult emitResult = tryEmitValue(null); + if (emitResult.isSuccess()) { + return; + } + + boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_COMPLETE, + emitResult); + if (shouldRetry) { + continue; + } + + switch (emitResult) { + case FAIL_ZERO_SUBSCRIBER: + case FAIL_OVERFLOW: + case FAIL_CANCELLED: + case FAIL_TERMINATED: + return; + case FAIL_NON_SERIALIZED: + throw new EmissionException(emitResult, + "Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially." + ); + default: + throw new EmissionException(emitResult, "Unknown emitResult value"); + } + } + } + + @Override + public final void onError(Throwable cause) { + for (;;) { + EmitResult emitResult = tryEmitError(cause); + if (emitResult.isSuccess()) { + return; + } + + boolean shouldRetry = EmitFailureHandler.FAIL_FAST.onEmitFailure(SignalType.ON_ERROR, + emitResult); + if (shouldRetry) { + continue; + } + + switch (emitResult) { + case FAIL_ZERO_SUBSCRIBER: + case FAIL_OVERFLOW: + case FAIL_CANCELLED: + return; + case FAIL_TERMINATED: + Operators.onErrorDropped(cause, currentContext()); + return; + case FAIL_NON_SERIALIZED: + throw new EmissionException(emitResult, + "Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially." + ); + default: + throw new EmissionException(emitResult, "Unknown emitResult value"); + } + } + } + + @SuppressWarnings("unchecked") + Sinks.EmitResult tryEmitError(Throwable cause) { + Objects.requireNonNull(cause, "onError cannot be null"); + + if (UPSTREAM.getAndSet(this, Operators.cancelledSubscription()) == Operators.cancelledSubscription()) { + return EmitResult.FAIL_TERMINATED; + } + + error = cause; + value = null; + source = null; + + //no need to double check since UPSTREAM.getAndSet gates the completion already + for (NextInner as : SUBSCRIBERS.getAndSet(this, TERMINATED)) { + as.onError(cause); + } + return EmitResult.OK; + } + + @Override + public final void onNext(@Nullable O value) { + if (value == null) { + emitEmpty(EmitFailureHandler.FAIL_FAST); + return; + } + + for (;;) { + EmitResult emitResult = tryEmitValue(value); + if (emitResult.isSuccess()) { + return; + } + + boolean shouldRetry = EmitFailureHandler.FAIL_FAST.onEmitFailure(SignalType.ON_NEXT, + emitResult); + if (shouldRetry) { + continue; + } + + switch (emitResult) { + case FAIL_ZERO_SUBSCRIBER: + //we want to "discard" without rendering the sink terminated. + // effectively NO-OP cause there's no subscriber, so no context :( + return; + case FAIL_OVERFLOW: + Operators.onDiscard(value, currentContext()); + //the emitError will onErrorDropped if already terminated + onError(Exceptions.failWithOverflow("Backpressure overflow during Sinks.Many#emitNext")); + return; + case FAIL_CANCELLED: + Operators.onDiscard(value, currentContext()); + return; + case FAIL_TERMINATED: + Operators.onNextDropped(value, currentContext()); + return; + case FAIL_NON_SERIALIZED: + throw new EmissionException(emitResult, + "Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially." + ); + default: + throw new EmissionException(emitResult, "Unknown emitResult value"); + } + } + } + + EmitResult tryEmitValue(@Nullable O value) { + Subscription s; + if ((s = UPSTREAM.getAndSet(this, Operators.cancelledSubscription())) == Operators.cancelledSubscription()) { + return EmitResult.FAIL_TERMINATED; + } + + this.value = value; + Publisher parent = source; + source = null; + + @SuppressWarnings("unchecked") NextInner[] array = SUBSCRIBERS.getAndSet(this, TERMINATED); + + if (value == null) { + for (NextInner as : array) { + as.onComplete(); + } + } + else { + if (s != null && !(parent instanceof Mono)) { + s.cancel(); + } + + for (NextInner as : array) { + as.complete(value); + } + } + return EmitResult.OK; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) + return subscription; + return super.scanUnsafe(key); + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + @Override + public long downstreamCount() { + return subscribers.length; + } + + @Override + @SuppressWarnings("unchecked") + public void dispose() { + Subscription s = UPSTREAM.getAndSet(this, Operators.cancelledSubscription()); + if (s == Operators.cancelledSubscription()) { + return; + } + + source = null; + if (s != null) { + s.cancel(); + } + + + NextInner[] a; + if ((a = SUBSCRIBERS.getAndSet(this, TERMINATED)) != TERMINATED) { + Exception e = new CancellationException("Disposed"); + error = e; + value = null; + + for (NextInner as : a) { + as.onError(e); + } + } + } + + @Override + // This method is inherited from a deprecated class and will be removed in 3.5. + @SuppressWarnings("deprecation") + public void cancel() { + if (isTerminated()) { + return; + } + + Subscription s = UPSTREAM.getAndSet(this, Operators.cancelledSubscription()); + if (s == Operators.cancelledSubscription()) { + return; + } + + source = null; + if (s != null) { + s.cancel(); + } + } + + @Override + public final void onSubscribe(Subscription subscription) { + if (Operators.setOnce(UPSTREAM, this, subscription)) { + subscription.request(Long.MAX_VALUE); + } + } + + @Override + // This method is inherited from a deprecated class and will be removed in 3.5. + @SuppressWarnings("deprecation") + public boolean isCancelled() { + return subscription == Operators.cancelledSubscription() && !isTerminated(); + } + + @Override + public boolean isTerminated() { + return subscribers == TERMINATED; + } + + @Nullable + @Override + public Throwable getError() { + return error; + } + + boolean add(NextInner ps) { + for (; ; ) { + NextInner[] a = subscribers; + + if (a == TERMINATED) { + return false; + } + + int n = a.length; + @SuppressWarnings("unchecked") NextInner[] b = new NextInner[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = ps; + + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + Publisher parent = source; + if (parent != null && a == EMPTY_WITH_SOURCE) { + parent.subscribe(this); + } + return true; + } + } + } + + @SuppressWarnings("unchecked") + void remove(NextInner ps) { + for (; ; ) { + NextInner[] a = subscribers; + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == ps) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + NextInner[] b; + boolean disconnect = false; + if (n == 1) { + if (isRefCounted && source != null) { + b = EMPTY_WITH_SOURCE; + disconnect = true; + } + else { + b = EMPTY; + } + } + else { + b = new NextInner[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)) { + if (disconnect) { + Subscription oldSubscription = UPSTREAM.getAndSet(this, null); + if (oldSubscription != null) { + oldSubscription.cancel(); + } + } + return; + } + } + } + + @Override + public void subscribe(final CoreSubscriber actual) { + NextInner as = new NextInner<>(actual, this); + actual.onSubscribe(as); + if (add(as)) { + if (as.isCancelled()) { + remove(as); + } + } + else { + Throwable ex = error; + if (ex != null) { + actual.onError(ex); + } + else { + O v = value; + if (v != null) { + as.complete(v); + } + else { + as.onComplete(); + } + } + } + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + void connect() { + Publisher parent = source; + if (parent != null && SUBSCRIBERS.compareAndSet(this, EMPTY_WITH_SOURCE, EMPTY)) { + parent.subscribe(this); + } + } + + final static class NextInner extends Operators.MonoSubscriber { + final NextProcessor parent; + + NextInner(CoreSubscriber actual, NextProcessor parent) { + super(actual); + this.parent = parent; + } + + @Override + public void cancel() { + if (STATE.getAndSet(this, CANCELLED) != CANCELLED) { + parent.remove(this); + } + } + + @Override + public void onComplete() { + if (!isCancelled()) { + actual.onComplete(); + } + } + + @Override + public void onError(Throwable t) { + if (!isCancelled()) { + actual.onError(t); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return parent; + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return super.scanUnsafe(key); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/OnNextFailureStrategy.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/OnNextFailureStrategy.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/OnNextFailureStrategy.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2018-2021 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.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import org.reactivestreams.Subscription; +import reactor.core.Exceptions; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A strategy to evaluate if errors that happens during an operator's onNext call can + * be recovered from, allowing the sequence to continue. This opt-in strategy is + * applied by compatible operators through the {@link Operators#onNextError(Object, Throwable, Context, Subscription)} + * and {@link Operators#onNextPollError(Object, Throwable, Context)} methods. + * See {@link #stop()}, {@link #resumeDrop()}, {@link #resumeDropIf(Predicate)}, + * {@link #resume(BiConsumer)} and {@link #resumeIf(Predicate, BiConsumer)} + * for the possible strategies. + * + * @author Simon Baslé + */ +interface OnNextFailureStrategy extends BiFunction, + BiPredicate { + + /** + * The key that can be used to store an {@link OnNextFailureStrategy} in a {@link Context}. + */ + String KEY_ON_NEXT_ERROR_STRATEGY = "reactor.onNextError.localStrategy"; + + @Override + @Nullable + default Throwable apply(Throwable throwable, @Nullable Object o) { + return process(throwable, o, Context.empty()); + } + + @Override + boolean test(Throwable throwable, @Nullable Object o); + + /** + * Process an error and the optional value that caused it (when applicable) in + * preparation for sequence resume, so that the error is not completely swallowed. + *

+ * If the strategy cannot resume this kind of error (ie. {@link #test(Throwable, Object)} + * returns false), return the original error. Any exception in the processing will be + * caught and returned. If the strategy was able to process the error correctly, + * returns null. + * + * @param error the error being recovered from. + * @param value the value causing the error, null if not applicable. + * @param context the {@link Context} associated with the recovering sequence. + * @return null if the error was processed for resume, a {@link Throwable} to propagate + * otherwise. + * @see #test(Throwable, Object) + */ + @Nullable + Throwable process(Throwable error, @Nullable Object value, Context context); + + /** + * A strategy that never let any error resume. + */ + static OnNextFailureStrategy stop() { + return STOP; + } + + /** + * A strategy that let all error resume. When processing, the error is passed to the + * {@link Operators#onErrorDropped(Throwable, Context)} hook and the incriminating + * source value (if available) is passed to the {@link Operators#onNextDropped(Object, Context)} + * hook, allowing the sequence to continue with further values. + */ + static OnNextFailureStrategy resumeDrop() { + return RESUME_DROP; + } + + /** + * A strategy that let some error resume. When processing, the error is passed to the + * {@link Operators#onErrorDropped(Throwable, Context)} hook and the incriminating + * source value (if available) is passed to the {@link Operators#onNextDropped(Object, Context)} + * hook, allowing the sequence to continue with further values. + *

+ * Any exception thrown by the predicate is thrown as is. + * + * @param causePredicate the predicate to match in order to resume from an error. + */ + static OnNextFailureStrategy resumeDropIf(Predicate causePredicate) { + return new ResumeDropStrategy(causePredicate); + } + + /** + * A strategy that let all errors resume. When processing, the error and the + * incriminating source value are passed to custom {@link Consumer Consumers}. + *

+ * Any exception thrown by the consumers will suppress the original error and be + * returned for propagation. If the original error is fatal then it is thrown + * upon processing it (see {@link Exceptions#throwIfFatal(Throwable)}). + * + * @param errorConsumer the {@link BiConsumer} to process the recovered errors with. + * It must deal with potential {@code null}s. + * @return a new {@link OnNextFailureStrategy} that allows resuming the sequence. + */ + static OnNextFailureStrategy resume(BiConsumer errorConsumer) { + return new ResumeStrategy(null, errorConsumer); + } + + /** + * A strategy that let some errors resume. When processing, the error and the + * incriminating source value are passed to custom {@link Consumer Consumers}. + *

+ * Any exception thrown by the predicate is thrown as is. Any exception thrown by + * the consumers will suppress the original error and be returned for propagation. + * Even if the original error is fatal, if it passes the predicate then it can + * be processed and recovered from (see {@link Exceptions#throwIfFatal(Throwable)}). + * + * @param causePredicate the {@link Predicate} to use to determine if a failure + * should be recovered from. + * @param errorConsumer the {@link BiConsumer} to process the recovered errors with. + * It must deal with potential {@code null}s. + * @return a new {@link OnNextFailureStrategy} that allows resuming the sequence. + */ + static OnNextFailureStrategy resumeIf( + Predicate causePredicate, + BiConsumer errorConsumer) { + return new ResumeStrategy(causePredicate, errorConsumer); + } + + //==== IMPLEMENTATIONS ==== + OnNextFailureStrategy STOP = new OnNextFailureStrategy() { + + @Override + public boolean test(Throwable error, @Nullable Object value) { + return false; + } + + @Override + public Throwable process(Throwable error, @Nullable Object value, Context context) { + Exceptions.throwIfFatal(error); + Throwable iee = new IllegalStateException("STOP strategy cannot process errors"); + iee.addSuppressed(error); + return iee; + } + }; + + OnNextFailureStrategy RESUME_DROP = new ResumeDropStrategy(null); + + final class ResumeStrategy implements OnNextFailureStrategy { + + final Predicate errorPredicate; + final BiConsumer errorConsumer; + + ResumeStrategy(@Nullable Predicate errorPredicate, + BiConsumer errorConsumer) { + this.errorPredicate = errorPredicate; + this.errorConsumer = errorConsumer; + } + + @Override + public boolean test(Throwable error, @Nullable Object value) { + return errorPredicate == null || errorPredicate.test(error); + } + + @Override + @Nullable + public Throwable process(Throwable error, @Nullable Object value, Context context) { + if (errorPredicate == null) { + Exceptions.throwIfFatal(error); + } + else if (!errorPredicate.test(error)) { + Exceptions.throwIfFatal(error); + return error; + } + try { + errorConsumer.accept(error, value); + return null; + } + catch (Throwable e) { + return Exceptions.addSuppressed(e, error); + } + } + } + + final class ResumeDropStrategy implements OnNextFailureStrategy { + + final Predicate errorPredicate; + + ResumeDropStrategy(@Nullable Predicate errorPredicate) { + this.errorPredicate = errorPredicate; + } + + @Override + public boolean test(Throwable error, @Nullable Object value) { + return errorPredicate == null || errorPredicate.test(error); + } + + @Override + @Nullable + public Throwable process(Throwable error, @Nullable Object value, Context context) { + if (errorPredicate == null) { + Exceptions.throwIfFatal(error); + } + else if (!errorPredicate.test(error)) { + Exceptions.throwIfFatal(error); + return error; + } + try { + if (value != null) { + Operators.onNextDropped(value, context); + } + Operators.onErrorDropped(error, context); + return null; + } + catch (Throwable e) { + return Exceptions.addSuppressed(e, error); + } + } + } + + final class LambdaOnNextErrorStrategy implements OnNextFailureStrategy { + + private final BiFunction delegateProcessor; + private final BiPredicate delegatePredicate; + + @SuppressWarnings("unchecked") + public LambdaOnNextErrorStrategy( + BiFunction delegateProcessor) { + this.delegateProcessor = delegateProcessor; + if (delegateProcessor instanceof BiPredicate) { + this.delegatePredicate = (BiPredicate) delegateProcessor; + } + else { + this.delegatePredicate = (e, v) -> true; + } + } + + @Override + public boolean test(Throwable error, @Nullable Object value) { + return delegatePredicate.test(error, value); + } + + @Override + @Nullable + public Throwable process(Throwable error, @Nullable Object value, Context ignored) { + return delegateProcessor.apply(error, value); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/OperatorDisposables.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/OperatorDisposables.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/OperatorDisposables.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2017-2021 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.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; + +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.util.annotation.Nullable; + +/** + * Utility methods to work with {@link Disposable} atomically. + * + * @author Simon Baslé + * @author David Karnok + */ +final class OperatorDisposables { + + /** + * A singleton {@link Disposable} that represents a disposed instance. Should not be + * leaked to clients. + */ + //NOTE: There is a private similar DISPOSED singleton in DefaultDisposable as well + static final Disposable DISPOSED = Disposables.disposed(); + + /** + * Atomically set the field to a {@link Disposable} and dispose the old content. + * + * @param updater the target field updater + * @param holder the target instance holding the field + * @param newValue the new Disposable to set + * @return true if successful, false if the field contains the {@link #DISPOSED} instance. + */ + public static boolean set(AtomicReferenceFieldUpdater updater, T holder, @Nullable Disposable newValue) { + for (;;) { + Disposable current = updater.get(holder); + if (current == DISPOSED) { + if (newValue != null) { + newValue.dispose(); + } + return false; + } + if (updater.compareAndSet(holder, current, newValue)) { + if (current != null) { + current.dispose(); + } + return true; + } + } + } + + /** + * Atomically set the field to the given non-null {@link Disposable} and return true, + * or return false if the field is non-null. + * If the target field contains the common {@link #DISPOSED} instance, the supplied disposable + * is disposed. If the field contains other non-null {@link Disposable}, an {@link IllegalStateException} + * is signalled to the {@code errorCallback}. + * + * @param updater the target field updater + * @param holder the target instance holding the field + * @param newValue the new Disposable to set, not null + * @return true if the operation succeeded, false + */ + public static boolean setOnce(AtomicReferenceFieldUpdater updater, T holder, Disposable newValue, + Consumer errorCallback) { + Objects.requireNonNull(newValue, "newValue is null"); + if (!updater.compareAndSet(holder, null, newValue)) { + newValue.dispose(); + if (updater.get(holder) != DISPOSED) { + errorCallback.accept(new IllegalStateException("Disposable already pushed")); + } + return false; + } + return true; + } + + /** + * Atomically replace the {@link Disposable} in the field with the given new Disposable + * but do not dispose the old one. + * + * @param updater the target field updater + * @param holder the target instance holding the field + * @param newValue the new Disposable to set, null allowed + * @return true if the operation succeeded, false if the target field contained + * the common {@link #DISPOSED} instance and the given disposable is not null but is disposed. + */ + public static boolean replace(AtomicReferenceFieldUpdater updater, T holder, @Nullable Disposable newValue) { + for (;;) { + Disposable current = updater.get(holder); + if (current == DISPOSED) { + if (newValue != null) { + newValue.dispose(); + } + return false; + } + if (updater.compareAndSet(holder, current, newValue)) { + return true; + } + } + } + + /** + * Atomically dispose the {@link Disposable} in the field if not already disposed. + * + * @param updater the target field updater + * @param holder the target instance holding the field + * @return true if the {@link Disposable} held by the field was properly disposed + */ + public static boolean dispose(AtomicReferenceFieldUpdater updater, T holder) { + Disposable current = updater.get(holder); + Disposable d = DISPOSED; + if (current != d) { + current = updater.getAndSet(holder, d); + if (current != d) { + if (current != null) { + current.dispose(); + } + return true; + } + } + return false; + } + + /** + * Verify that current is null and next is not null, otherwise signal a + * {@link NullPointerException} to the {@code errorCallback} and return false. + * + * @param current the current {@link Disposable}, expected to be null + * @param next the next {@link Disposable}, expected to be non-null + * @return true if the validation succeeded + */ + public static boolean validate(@Nullable Disposable current, Disposable next, + Consumer errorCallback) { + //noinspection ConstantConditions + if (next == null) { + errorCallback.accept(new NullPointerException("next is null")); + return false; + } + if (current != null) { + next.dispose(); + errorCallback.accept(new IllegalStateException("Disposable already pushed")); + return false; + } + return true; + } + + /** + * Atomically try to set the given {@link Disposable} on the field if it is null or + * disposes it if the field contains {@link #DISPOSED}. + * + * @param updater the target field updater + * @param holder the target instance holding the field + * @param newValue the disposable to set + * @return true if successful, false otherwise + */ + public static boolean trySet(AtomicReferenceFieldUpdater updater, T holder, Disposable newValue) { + if (!updater.compareAndSet(holder, null, newValue)) { + if (updater.get(holder) == DISPOSED) { + newValue.dispose(); + } + return false; + } + return true; + } + + /** + * Check if the given {@link Disposable} is the singleton {@link #DISPOSED}. + * + * @param d the disposable to check + * @return true if d is {@link #DISPOSED} + */ + public static boolean isDisposed(Disposable d) { + return d == DISPOSED; + } + +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/Operators.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/Operators.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/Operators.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,2828 @@ +/* + * Copyright (c) 2016-2021 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.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.Queue; +import java.util.Spliterator; +import java.util.concurrent.RejectedExecutionException; +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.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Fuseable; +import reactor.core.Fuseable.QueueSubscription; +import reactor.core.Scannable; +import reactor.core.Scannable.Attr.RunStyle; +import reactor.util.Logger; +import reactor.util.Loggers; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +import static reactor.core.Fuseable.NONE; + +/** + * A helper to support "Operator" writing, handle noop subscriptions, validate request + * size and to cap concurrent additive operations to Long.MAX_VALUE, + * which is generic to {@link Subscription#request(long)} handling. + * + * Combine utils available to operator implementations, @see https://github.com/reactor/reactive-streams-commons + * + */ +public abstract class Operators { + + /** + * Cap an addition to Long.MAX_VALUE + * + * @param a left operand + * @param b right operand + * + * @return Addition result or Long.MAX_VALUE if overflow + */ + public static long addCap(long a, long b) { + long res = a + b; + if (res < 0L) { + return Long.MAX_VALUE; + } + return res; + } + + /** + * Concurrent addition bound to Long.MAX_VALUE. + * Any concurrent write will "happen before" this operation. + * + * @param the parent instance type + * @param updater current field updater + * @param instance current instance to update + * @param toAdd delta to add + * @return value before addition or Long.MAX_VALUE + */ + public static long addCap(AtomicLongFieldUpdater updater, T instance, long toAdd) { + long r, u; + for (;;) { + r = updater.get(instance); + if (r == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + u = addCap(r, toAdd); + if (updater.compareAndSet(instance, r, u)) { + return r; + } + } + } + /** + * Returns the subscription as QueueSubscription if possible or null. + * @param the value type of the QueueSubscription. + * @param s the source subscription to try to convert. + * @return the QueueSubscription instance or null + */ + @SuppressWarnings("unchecked") + @Nullable + public static QueueSubscription as(Subscription s) { + if (s instanceof QueueSubscription) { + return (QueueSubscription) s; + } + return null; + } + + /** + * A singleton Subscription that represents a cancelled subscription instance and + * should not be leaked to clients as it represents a terminal state.
If + * algorithms need to hand out a subscription, replace this with a singleton + * subscription because there is no standard way to tell if a Subscription is cancelled + * or not otherwise. + * + * @return a singleton noop {@link Subscription} to be used as an inner representation + * of the cancelled state + */ + public static Subscription cancelledSubscription() { + return CancelledSubscription.INSTANCE; + } + + /** + * Calls onSubscribe on the target Subscriber with the empty instance followed by a call to onComplete. + * + * @param s the target subscriber + */ + public static void complete(Subscriber s) { + s.onSubscribe(EmptySubscription.INSTANCE); + s.onComplete(); + } + + /** + * Return a singleton {@link Subscriber} that does not check for double onSubscribe + * and purely request Long.MAX. If an error is received it will raise a + * {@link Exceptions#errorCallbackNotImplemented(Throwable)} in the receiving thread. + * + * @return a new {@link Subscriber} whose sole purpose is to request Long.MAX + */ + @SuppressWarnings("unchecked") + public static CoreSubscriber drainSubscriber() { + return (CoreSubscriber)DrainSubscriber.INSTANCE; + } + + /** + * A {@link Subscriber} that is expected to be used as a placeholder and + * never actually be called. All methods log an error. + * + * @param the type of data (ignored) + * @return a placeholder subscriber + */ + @SuppressWarnings("unchecked") + public static CoreSubscriber emptySubscriber() { + return (CoreSubscriber) EMPTY_SUBSCRIBER; + } + + /** + * A singleton enumeration that represents a no-op Subscription instance that + * can be freely given out to clients. + *

+ * The enum also implements Fuseable.QueueSubscription so operators expecting a + * QueueSubscription from a Fuseable source don't have to double-check their Subscription + * received in onSubscribe. + * + * @return a singleton noop {@link Subscription} + */ + public static Subscription emptySubscription() { + return EmptySubscription.INSTANCE; + } + + /** + * Check whether the provided {@link Subscription} is the one used to satisfy Spec's §1.9 rule + * before signalling an error. + * + * @param subscription the subscription to test. + * @return true if passed subscription is a subscription created in {@link #reportThrowInSubscribe(CoreSubscriber, Throwable)}. + */ + public static boolean canAppearAfterOnSubscribe(Subscription subscription) { + return subscription == EmptySubscription.FROM_SUBSCRIBE_INSTANCE; + } + + /** + * Calls onSubscribe on the target Subscriber with the empty instance followed by a call to onError with the + * supplied error. + * + * @param s target Subscriber to error + * @param e the actual error + */ + public static void error(Subscriber s, Throwable e) { + s.onSubscribe(EmptySubscription.INSTANCE); + s.onError(e); + } + + /** + * Report a {@link Throwable} that was thrown from a call to {@link Publisher#subscribe(Subscriber)}, + * attempting to notify the {@link Subscriber} by: + *

    + *
  1. providing a special {@link Subscription} via {@link Subscriber#onSubscribe(Subscription)}
  2. + *
  3. immediately delivering an {@link Subscriber#onError(Throwable) onError} signal after that
  4. + *
+ *

+ * As at that point the subscriber MAY have already been provided with a {@link Subscription}, we + * assume most well formed subscribers will ignore this second {@link Subscription} per Reactive + * Streams rule 1.9. Subscribers that don't usually ignore may recognize this special case and ignore + * it by checking {@link #canAppearAfterOnSubscribe(Subscription)}. + *

+ * Note that if the {@link Subscriber#onSubscribe(Subscription) onSubscribe} attempt throws, + * {@link Exceptions#throwIfFatal(Throwable) fatal} exceptions are thrown. Other exceptions + * are added as {@link Throwable#addSuppressed(Throwable) suppressed} on the original exception, + * which is then directly notified as an {@link Subscriber#onError(Throwable) onError} signal + * (again assuming that such exceptions occur because a {@link Subscription} is already set). + * + * @param subscriber the {@link Subscriber} being subscribed when the error happened + * @param e the {@link Throwable} that was thrown from {@link Publisher#subscribe(Subscriber)} + * @see #canAppearAfterOnSubscribe(Subscription) + */ + public static void reportThrowInSubscribe(CoreSubscriber subscriber, Throwable e) { + try { + subscriber.onSubscribe(EmptySubscription.FROM_SUBSCRIBE_INSTANCE); + } + catch (Throwable onSubscribeError) { + Exceptions.throwIfFatal(onSubscribeError); + e.addSuppressed(onSubscribeError); + } + subscriber.onError(onOperatorError(e, subscriber.currentContext())); + } + + /** + * Create a function that can be used to support a custom operator via + * {@link CoreSubscriber} decoration. The function is compatible with + * {@link Flux#transform(Function)}, {@link Mono#transform(Function)}, + * {@link Hooks#onEachOperator(Function)} and {@link Hooks#onLastOperator(Function)}, + * but requires that the original {@link Publisher} be {@link Scannable}. + *

+ * This variant attempts to expose the {@link Publisher} as a {@link Scannable} for + * convenience of introspection. You should however avoid instanceof checks or any + * other processing that depends on identity of the {@link Publisher}, as it might + * get hidden if {@link Scannable#isScanAvailable()} returns {@code false}. + * Use {@link #liftPublisher(BiFunction)} instead for that kind of use case. + * + * @param lifter the bifunction taking {@link Scannable} from the enclosing + * publisher (assuming it is compatible) and consuming {@link CoreSubscriber}. + * It must return a receiving {@link CoreSubscriber} that will immediately subscribe + * to the applied {@link Publisher}. + * + * @param the input type + * @param the output type + * + * @return a new {@link Function} + * @see #liftPublisher(BiFunction) + */ + public static Function, ? extends Publisher> lift(BiFunction, ? extends CoreSubscriber> lifter) { + return LiftFunction.liftScannable(null, lifter); + } + + /** + * Create a function that can be used to support a custom operator via + * {@link CoreSubscriber} decoration. The function is compatible with + * {@link Flux#transform(Function)}, {@link Mono#transform(Function)}, + * {@link Hooks#onEachOperator(Function)} and {@link Hooks#onLastOperator(Function)}, + * but requires that the original {@link Publisher} be {@link Scannable}. + *

+ * This variant attempts to expose the {@link Publisher} as a {@link Scannable} for + * convenience of introspection. You should however avoid instanceof checks or any + * other processing that depends on identity of the {@link Publisher}, as it might + * get hidden if {@link Scannable#isScanAvailable()} returns {@code false}. + * Use {@link #liftPublisher(Predicate, BiFunction)} instead for that kind of use case. + * + *

+ * The function will be invoked only if the passed {@link Predicate} matches. + * Therefore the transformed type O must be the same than the input type since + * unmatched predicate will return the applied {@link Publisher}. + * + * @param filter the predicate to match taking {@link Scannable} from the applied + * publisher to operate on. Assumes original is scan-compatible. + * @param lifter the bifunction taking {@link Scannable} from the enclosing + * publisher and consuming {@link CoreSubscriber}. It must return a receiving + * {@link CoreSubscriber} that will immediately subscribe to the applied + * {@link Publisher}. Assumes the original is scan-compatible. + * + * @param the input and output type + * + * @return a new {@link Function} + * @see #liftPublisher(Predicate, BiFunction) + */ + public static Function, ? extends Publisher> lift( + Predicate filter, + BiFunction, ? extends CoreSubscriber> lifter) { + return LiftFunction.liftScannable(filter, lifter); + } + + /** + * Create a function that can be used to support a custom operator via + * {@link CoreSubscriber} decoration. The function is compatible with + * {@link Flux#transform(Function)}, {@link Mono#transform(Function)}, + * {@link Hooks#onEachOperator(Function)} and {@link Hooks#onLastOperator(Function)}, + * and works with the raw {@link Publisher} as input, which is useful if you need to + * detect the precise type of the source (eg. instanceof checks to detect Mono, Flux, + * true Scannable, etc...). + * + * @param lifter the bifunction taking the raw {@link Publisher} and + * {@link CoreSubscriber}. The publisher can be double-checked (including with + * {@code instanceof}, and the function must return a receiving {@link CoreSubscriber} + * that will immediately subscribe to the {@link Publisher}. + * + * @param the input type + * @param the output type + * + * @return a new {@link Function} + */ + public static Function, ? extends Publisher> liftPublisher( + BiFunction, ? extends CoreSubscriber> lifter) { + return LiftFunction.liftPublisher(null, lifter); + } + + /** + * Create a function that can be used to support a custom operator via + * {@link CoreSubscriber} decoration. The function is compatible with + * {@link Flux#transform(Function)}, {@link Mono#transform(Function)}, + * {@link Hooks#onEachOperator(Function)} and {@link Hooks#onLastOperator(Function)}, + * and works with the raw {@link Publisher} as input, which is useful if you need to + * detect the precise type of the source (eg. instanceof checks to detect Mono, Flux, + * true Scannable, etc...). + * + *

+ * The function will be invoked only if the passed {@link Predicate} matches. + * Therefore the transformed type O must be the same than the input type since + * unmatched predicate will return the applied {@link Publisher}. + * + * @param filter the {@link Predicate} that the raw {@link Publisher} must pass for + * the transformation to occur + * @param lifter the {@link BiFunction} taking the raw {@link Publisher} and + * {@link CoreSubscriber}. The publisher can be double-checked (including with + * {@code instanceof}, and the function must return a receiving {@link CoreSubscriber} + * that will immediately subscribe to the {@link Publisher}. + * + * @param the input and output type + * + * @return a new {@link Function} + */ + public static Function, ? extends Publisher> liftPublisher( + Predicate filter, + BiFunction, ? extends CoreSubscriber> lifter) { + return LiftFunction.liftPublisher(filter, lifter); + } + + /** + * Cap a multiplication to Long.MAX_VALUE + * + * @param a left operand + * @param b right operand + * + * @return Product result or Long.MAX_VALUE if overflow + */ + public static long multiplyCap(long a, long b) { + long u = a * b; + if (((a | b) >>> 31) != 0) { + if (u / a != b) { + return Long.MAX_VALUE; + } + } + return u; + } + + /** + * Create an adapter for local onDiscard hooks that check the element + * being discarded is of a given {@link Class}. The resulting {@link Function} adds the + * hook to the {@link Context}, potentially chaining it to an existing hook in the {@link Context}. + * + * @param type the type of elements to take into account + * @param discardHook the discarding handler for this type of elements + * @param element type + * @return a {@link Function} that can be used to modify a {@link Context}, adding or + * updating a context-local discard hook. + */ + static final Function discardLocalAdapter(Class type, Consumer discardHook) { + Objects.requireNonNull(type, "onDiscard must be based on a type"); + Objects.requireNonNull(discardHook, "onDiscard must be provided a discardHook Consumer"); + + final Consumer safeConsumer = obj -> { + if (type.isInstance(obj)) { + discardHook.accept(type.cast(obj)); + } + }; + + return ctx -> { + Consumer consumer = ctx.getOrDefault(Hooks.KEY_ON_DISCARD, null); + if (consumer == null) { + return ctx.put(Hooks.KEY_ON_DISCARD, safeConsumer); + } + else { + return ctx.put(Hooks.KEY_ON_DISCARD, safeConsumer.andThen(consumer)); + } + }; + } + + /** + * Utility method to activate the onDiscard feature (see {@link Flux#doOnDiscard(Class, Consumer)}) + * in a target {@link Context}. Prefer using the {@link Flux} API, and reserve this for + * testing purposes. + * + * @param target the original {@link Context} + * @param discardConsumer the consumer that will be used to cleanup discarded elements + * @return a new {@link Context} that holds (potentially combined) cleanup {@link Consumer} + */ + public static final Context enableOnDiscard(@Nullable Context target, Consumer discardConsumer) { + Objects.requireNonNull(discardConsumer, "discardConsumer must be provided"); + if (target == null) { + return Context.of(Hooks.KEY_ON_DISCARD, discardConsumer); + } + return target.put(Hooks.KEY_ON_DISCARD, discardConsumer); + } + + /** + * Invoke a (local or global) hook that processes elements that get discarded. This + * includes elements that are dropped (for malformed sources), but also filtered out + * (eg. not passing a {@code filter()} predicate). + *

+ * For elements that are buffered or enqueued, but subsequently discarded due to + * cancellation or error, see {@link #onDiscardMultiple(Stream, Context)} and + * {@link #onDiscardQueueWithClear(Queue, Context, Function)}. + * + * @param element the element that is being discarded + * @param context the context in which to look for a local hook + * @param the type of the element + * @see #onDiscardMultiple(Stream, Context) + * @see #onDiscardMultiple(Collection, Context) + * @see #onDiscardQueueWithClear(Queue, Context, Function) + */ + public static void onDiscard(@Nullable T element, Context context) { + Consumer hook = context.getOrDefault(Hooks.KEY_ON_DISCARD, null); + if (element != null && hook != null) { + try { + hook.accept(element); + } + catch (Throwable t) { + log.warn("Error in discard hook", t); + } + } + } + + /** + * Invoke a (local or global) hook that processes elements that get discarded + * en masse after having been enqueued, due to cancellation or error. This method + * also empties the {@link Queue} (either by repeated {@link Queue#poll()} calls if + * a hook is defined, or by {@link Queue#clear()} as a shortcut if no hook is defined). + * + * @param queue the queue that is being discarded and cleared + * @param context the context in which to look for a local hook + * @param extract an optional extractor method for cases where the queue doesn't + * directly contain the elements to discard + * @param the type of the element + * @see #onDiscardMultiple(Stream, Context) + * @see #onDiscardMultiple(Collection, Context) + * @see #onDiscard(Object, Context) + */ + public static void onDiscardQueueWithClear( + @Nullable Queue queue, + Context context, + @Nullable Function> extract) { + if (queue == null) { + return; + } + + Consumer hook = context.getOrDefault(Hooks.KEY_ON_DISCARD, null); + if (hook == null) { + queue.clear(); + return; + } + + try { + for(;;) { + T toDiscard = queue.poll(); + if (toDiscard == null) { + break; + } + + if (extract != null) { + try { + extract.apply(toDiscard) + .forEach(elementToDiscard -> { + try { + hook.accept(elementToDiscard); + } + catch (Throwable t) { + log.warn("Error while discarding item extracted from a queue element, continuing with next item", t); + } + }); + } + catch (Throwable t) { + log.warn("Error while extracting items to discard from queue element, continuing with next queue element", t); + } + } + else { + try { + hook.accept(toDiscard); + } + catch (Throwable t) { + log.warn("Error while discarding a queue element, continuing with next queue element", t); + } + } + } + } + catch (Throwable t) { + log.warn("Cannot further apply discard hook while discarding and clearing a queue", t); + } + } + + /** + * Invoke a (local or global) hook that processes elements that get discarded en masse. + * This includes elements that are buffered but subsequently discarded due to + * cancellation or error. + * + * @param multiple the collection of elements to discard (possibly extracted from other + * collections/arrays/queues) + * @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(Stream multiple, Context context) { + Consumer hook = context.getOrDefault(Hooks.KEY_ON_DISCARD, null); + if (hook != null) { + try { + multiple.filter(Objects::nonNull) + .forEach(v -> { + try { + hook.accept(v); + } + catch (Throwable t) { + log.warn("Error while discarding a stream element, continuing with next element", t); + } + }); + } + catch (Throwable t) { + log.warn("Error while discarding stream, stopping", t); + } + } + } + + /** + * Invoke a (local or global) hook that processes elements that get discarded en masse. + * This includes elements that are buffered but subsequently discarded due to + * cancellation or error. + * + * @param multiple the collection of elements to discard + * @param context the {@link Context} in which to look for local hook + * @see #onDiscard(Object, Context) + * @see #onDiscardMultiple(Stream, Context) + * @see #onDiscardQueueWithClear(Queue, Context, Function) + */ + public static void onDiscardMultiple(@Nullable Collection multiple, Context context) { + if (multiple == null) return; + Consumer hook = context.getOrDefault(Hooks.KEY_ON_DISCARD, null); + if (hook != null) { + try { + if (multiple.isEmpty()) { + return; + } + for (Object o : multiple) { + if (o != null) { + try { + hook.accept(o); + } + catch (Throwable t) { + log.warn("Error while discarding element from a Collection, continuing with next element", t); + } + } + } + } + catch (Throwable t) { + log.warn("Error while discarding collection, stopping", t); + } + } + } + + /** + * Invoke a (local or global) hook that processes elements that remains in an {@link java.util.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()}. + * + * @param multiple the {@link Iterator} 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 Iterator 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 Iterator, continuing with next element", t); + } + } + }); + } + catch (Throwable t) { + log.warn("Error while discarding Iterator, 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)}. + * + * @param e the dropped exception + * @param context a context that might hold a local error consumer + */ + public static void onErrorDropped(Throwable e, Context context) { + Consumer hook = context.getOrDefault(Hooks.KEY_ON_ERROR_DROPPED,null); + if (hook == null) { + hook = Hooks.onErrorDroppedHook; + } + if (hook == null) { + log.error("Operator called default onErrorDropped", e); + return; + } + hook.accept(e); + } + + /** + * An unexpected event is about to be dropped. + *

+ * If no hook is registered for {@link Hooks#onNextDropped(Consumer)}, the dropped + * element is just logged at DEBUG level. + * + * @param the dropped value type + * @param t the dropped data + * @param context a context that might hold a local next consumer + */ + public static void onNextDropped(T t, Context context) { + Objects.requireNonNull(t, "onNext"); + Objects.requireNonNull(context, "context"); + Consumer hook = context.getOrDefault(Hooks.KEY_ON_NEXT_DROPPED, null); + if (hook == null) { + hook = Hooks.onNextDroppedHook; + } + if (hook != null) { + hook.accept(t); + } + else if (log.isDebugEnabled()) { + log.debug("onNextDropped: " + t); + } + } + + /** + * Map an "operator" error. The + * result error will be passed via onError to the operator downstream after + * checking for fatal error via + * {@link Exceptions#throwIfFatal(Throwable)}. + * + * @param error the callback or operator error + * @param context a context that might hold a local error consumer + * @return mapped {@link Throwable} + * + */ + public static Throwable onOperatorError(Throwable error, Context context) { + return onOperatorError(null, error, context); + } + + /** + * Map an "operator" error given an operator parent {@link Subscription}. The + * result error will be passed via onError to the operator downstream. + * {@link Subscription} will be cancelled after checking for fatal error via + * {@link Exceptions#throwIfFatal(Throwable)}. + * + * @param subscription the linked operator parent {@link Subscription} + * @param error the callback or operator error + * @param context a context that might hold a local error consumer + * @return mapped {@link Throwable} + * + */ + public static Throwable onOperatorError(@Nullable Subscription subscription, + Throwable error, + Context context) { + return onOperatorError(subscription, error, null, context); + } + + /** + * Map an "operator" error given an operator parent {@link Subscription}. The + * result error will be passed via onError to the operator downstream. + * {@link Subscription} will be cancelled after checking for fatal error via + * {@link Exceptions#throwIfFatal(Throwable)}. Takes an additional signal, which + * can be added as a suppressed exception if it is a {@link Throwable} and the + * default {@link Hooks#onOperatorError(BiFunction) hook} is in place. + * + * @param subscription the linked operator parent {@link Subscription} + * @param error the callback or operator error + * @param dataSignal the value (onNext or onError) signal processed during failure + * @param context a context that might hold a local error consumer + * @return mapped {@link Throwable} + * + */ + public static Throwable onOperatorError(@Nullable Subscription subscription, + Throwable error, + @Nullable Object dataSignal, Context context) { + + Exceptions.throwIfFatal(error); + if(subscription != null) { + subscription.cancel(); + } + + Throwable t = Exceptions.unwrap(error); + BiFunction hook = + context.getOrDefault(Hooks.KEY_ON_OPERATOR_ERROR, null); + if (hook == null) { + hook = Hooks.onOperatorErrorHook; + } + if (hook == null) { + if (dataSignal != null) { + if (dataSignal != t && dataSignal instanceof Throwable) { + t = Exceptions.addSuppressed(t, (Throwable) dataSignal); + } + //do not wrap original value to avoid strong references + /*else { + }*/ + } + return t; + } + return hook.apply(error, dataSignal); + } + + /** + * Return a wrapped {@link RejectedExecutionException} which can be thrown by the + * 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 + * {@link #onOperatorError(Subscription, Throwable, Object, Context)}. + * + * @param original the original execution error + * @param context a context that might hold a local error consumer + * + */ + public static RuntimeException onRejectedExecution(Throwable original, Context context) { + return onRejectedExecution(original, null, null, null, context); + } + + static final OnNextFailureStrategy onNextErrorStrategy(Context context) { + OnNextFailureStrategy strategy = null; + + BiFunction fn = context.getOrDefault( + OnNextFailureStrategy.KEY_ON_NEXT_ERROR_STRATEGY, null); + if (fn instanceof OnNextFailureStrategy) { + strategy = (OnNextFailureStrategy) fn; + } else if (fn != null) { + strategy = new OnNextFailureStrategy.LambdaOnNextErrorStrategy(fn); + } + + if (strategy == null) strategy = Hooks.onNextErrorHook; + if (strategy == null) strategy = OnNextFailureStrategy.STOP; + return strategy; + } + + public static final BiFunction onNextErrorFunction(Context context) { + return onNextErrorStrategy(context); + } + + /** + * Find the {@link OnNextFailureStrategy} to apply to the calling operator (which could be a local + * error mode defined in the {@link Context}) and apply it. For poll(), prefer + * {@link #onNextPollError(Object, Throwable, Context)} as it returns a {@link RuntimeException}. + *

+ * Cancels the {@link Subscription} and return a {@link Throwable} if errors are + * fatal for the error mode, in which case the operator should call onError with the + * returned error. On the contrary, if the error mode allows the sequence to + * continue, does not cancel the Subscription and returns {@code null}. + *

+ * Typical usage pattern differs depending on the calling method: + *

    + *
  • {@code onNext}: check for a throwable return value and call + * {@link Subscriber#onError(Throwable)} if not null, otherwise perform a direct + * {@link Subscription#request(long) request(1)} on the upstream.
  • + * + *
  • {@code tryOnNext}: check for a throwable return value and call + * {@link Subscriber#onError(Throwable)} if not null, otherwise + * return {@code false} to indicate value was not consumed and more must be + * tried.
  • + * + *
  • any of the above where the error is going to be propagated through onError but the + * subscription shouldn't be cancelled: use {@link #onNextError(Object, Throwable, Context)} instead.
  • + * + *
  • {@code poll} (where the error will be thrown): use {@link #onNextPollError(Object, Throwable, Context)} instead.
  • + *
+ * + * @param value The onNext value that caused an error. Can be null. + * @param error The error. + * @param context The most significant {@link Context} in which to look for an {@link OnNextFailureStrategy}. + * @param subscriptionForCancel The mandatory {@link Subscription} that should be cancelled if the + * strategy is terminal. See also {@link #onNextError(Object, Throwable, Context)} and + * {@link #onNextPollError(Object, Throwable, Context)} for alternatives that don't cancel a subscription + * @param The type of the value causing the error. + * @return a {@link Throwable} to propagate through onError if the strategy is + * terminal and cancelled the subscription, null if not. + */ + @Nullable + public static Throwable onNextError(@Nullable T value, Throwable error, Context context, + Subscription subscriptionForCancel) { + error = unwrapOnNextError(error); + OnNextFailureStrategy strategy = onNextErrorStrategy(context); + if (strategy.test(error, value)) { + //some strategies could still return an exception, eg. if the consumer throws + Throwable t = strategy.process(error, value, context); + if (t != null) { + subscriptionForCancel.cancel(); + } + return t; + } + else { + //falls back to operator errors + return onOperatorError(subscriptionForCancel, error, value, context); + } + } + + /** + * Find the {@link OnNextFailureStrategy} to apply to the calling async operator (which could be + * a local error mode defined in the {@link Context}) and apply it. + *

+ * This variant never cancels a {@link Subscription}. It returns a {@link Throwable} if the error is + * fatal for the error mode, in which case the operator should call onError with the + * returned error. On the contrary, if the error mode allows the sequence to + * continue, this method returns {@code null}. + * + * @param value The onNext value that caused an error. + * @param error The error. + * @param context The most significant {@link Context} in which to look for an {@link OnNextFailureStrategy}. + * @param The type of the value causing the error. + * @return a {@link Throwable} to propagate through onError if the strategy is terminal, null if not. + * @see #onNextError(Object, Throwable, Context, Subscription) + */ + @Nullable + public static Throwable onNextError(@Nullable T value, Throwable error, Context context) { + error = unwrapOnNextError(error); + OnNextFailureStrategy strategy = onNextErrorStrategy(context); + if (strategy.test(error, value)) { + //some strategies could still return an exception, eg. if the consumer throws + return strategy.process(error, value, context); + } + else { + return onOperatorError(null, error, value, context); + } + } + + /** + * Find the {@link OnNextFailureStrategy} to apply to the calling operator (which could be a local + * error mode defined in the {@link Context}) and apply it. + * + * @param error The error. + * @param context The most significant {@link Context} in which to look for an {@link OnNextFailureStrategy}. + * @param subscriptionForCancel The {@link Subscription} that should be cancelled if the + * strategy is terminal. Null to ignore (for poll, use {@link #onNextPollError(Object, Throwable, Context)} + * rather than passing null). + * @param The type of the value causing the error. + * @return a {@link Throwable} to propagate through onError if the strategy is + * terminal and cancelled the subscription, null if not. + */ + public static Throwable onNextInnerError(Throwable error, Context context, @Nullable Subscription subscriptionForCancel) { + error = unwrapOnNextError(error); + OnNextFailureStrategy strategy = onNextErrorStrategy(context); + if (strategy.test(error, null)) { + //some strategies could still return an exception, eg. if the consumer throws + Throwable t = strategy.process(error, null, context); + if (t != null && subscriptionForCancel != null) { + subscriptionForCancel.cancel(); + } + return t; + } + else { + return error; + } + } + + /** + * Find the {@link OnNextFailureStrategy} to apply to the calling async operator (which could be + * a local error mode defined in the {@link Context}) and apply it. + *

+ * Returns a {@link RuntimeException} if the error is fatal for the error mode, in which + * case the operator poll should throw the returned error. On the contrary if the + * error mode allows the sequence to continue, returns {@code null} in which case + * the operator should retry the {@link Queue#poll() poll()}. + *

+ * Note that this method {@link Exceptions#propagate(Throwable) wraps} checked exceptions in order to + * return a {@link RuntimeException} that can be thrown from an arbitrary method. If you don't want to + * throw the returned exception and this wrapping behavior is undesirable, but you still don't want to + * cancel a subscription, you can use {@link #onNextError(Object, Throwable, Context)} instead. + * + * @param value The onNext value that caused an error. + * @param error The error. + * @param context The most significant {@link Context} in which to look for an {@link OnNextFailureStrategy}. + * @param The type of the value causing the error. + * @return a {@link RuntimeException} to be thrown (eg. within {@link Queue#poll()} if the error is terminal in + * the strategy, null if not. + * @see #onNextError(Object, Throwable, Context) + */ + @Nullable + public static RuntimeException onNextPollError(@Nullable T value, Throwable error, Context context) { + error = unwrapOnNextError(error); + OnNextFailureStrategy strategy = onNextErrorStrategy(context); + if (strategy.test(error, value)) { + //some strategies could still return an exception, eg. if the consumer throws + Throwable t = strategy.process(error, value, context); + if (t != null) return Exceptions.propagate(t); + return null; + } + else { + Throwable t = onOperatorError(null, error, value, context); + return Exceptions.propagate(t); + } + } + + /** + * Applies the hooks registered with {@link Hooks#onLastOperator} and returns + * {@link CorePublisher} ready to be subscribed on. + * + * @param source the original {@link CorePublisher}. + * @param the type of the value. + * @return a {@link CorePublisher} to subscribe on. + */ + @SuppressWarnings("unchecked") + public static CorePublisher onLastAssembly(CorePublisher source) { + Function hook = Hooks.onLastOperatorHook; + if (hook == null) { + return source; + } + + Publisher publisher = Objects.requireNonNull(hook.apply(source),"LastOperator hook returned null"); + + if (publisher instanceof CorePublisher) { + return (CorePublisher) publisher; + } + else { + return new CorePublisherAdapter<>(publisher); + } + } + + private static Throwable unwrapOnNextError(Throwable error) { + return Exceptions.isBubbling(error) ? error : Exceptions.unwrap(error); + } + + /** + * Return a wrapped {@link RejectedExecutionException} which can be thrown by the + * 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#failWithRejected(Throwable)} and + * {@link #onOperatorError(Subscription, Throwable, Object, Context)} (with the passed + * {@link Subscription}). + * + * @param original the original execution error + * @param subscription the subscription to pass to onOperatorError. + * @param suppressed a Throwable to be suppressed by the {@link RejectedExecutionException} (or null if not relevant) + * @param dataSignal a value to be passed to {@link #onOperatorError(Subscription, Throwable, Object, Context)} (or null if not relevant) + * @param context a context that might hold a local error consumer + */ + public static RuntimeException onRejectedExecution(Throwable original, + @Nullable Subscription subscription, + @Nullable Throwable suppressed, + @Nullable Object dataSignal, + Context context) { + //we "cheat" to apply the special key for onRejectedExecution in onOperatorError + if (context.hasKey(Hooks.KEY_ON_REJECTED_EXECUTION)) { + context = context.put(Hooks.KEY_ON_OPERATOR_ERROR, context.get(Hooks.KEY_ON_REJECTED_EXECUTION)); + } + + //don't create REE if original is a reactor-produced REE (not including singletons) + RejectedExecutionException ree = Exceptions.failWithRejected(original); + if (suppressed != null) { + ree.addSuppressed(suppressed); + } + if (dataSignal != null) { + return Exceptions.propagate(Operators.onOperatorError(subscription, ree, + dataSignal, context)); + } + return Exceptions.propagate(Operators.onOperatorError(subscription, ree, context)); + } + + /** + * Concurrent subtraction bound to 0, mostly used to decrement a request tracker by + * the amount produced by the operator. Any concurrent write will "happen before" + * this operation. + * + * @param the parent instance type + * @param updater current field updater + * @param instance current instance to update + * @param toSub delta to subtract + * @return value after subtraction or zero + */ + public static long produced(AtomicLongFieldUpdater updater, T instance, long toSub) { + long r, u; + do { + r = updater.get(instance); + if (r == 0 || r == Long.MAX_VALUE) { + return r; + } + u = subOrZero(r, toSub); + } while (!updater.compareAndSet(instance, r, u)); + + return u; + } + + /** + * A generic utility to atomically replace a subscription or cancel the replacement + * if the current subscription is marked as already cancelled (as in + * {@link #cancelledSubscription()}). + * + * @param field The Atomic container + * @param instance the instance reference + * @param s the subscription + * @param the instance type + * + * @return true if replaced + */ + public static boolean replace(AtomicReferenceFieldUpdater field, + F instance, + Subscription s) { + for (; ; ) { + Subscription a = field.get(instance); + if (a == CancelledSubscription.INSTANCE) { + s.cancel(); + return false; + } + if (field.compareAndSet(instance, a, s)) { + return true; + } + } + } + + /** + * Log an {@link IllegalArgumentException} if the request is null or negative. + * + * @param n the failing demand + * + * @see Exceptions#nullOrNegativeRequestException(long) + */ + public static void reportBadRequest(long n) { + if (log.isDebugEnabled()) { + log.debug("Negative request", + Exceptions.nullOrNegativeRequestException(n)); + } + } + + /** + * Log an {@link IllegalStateException} that indicates more than the requested + * amount was produced. + * + * @see Exceptions#failWithOverflow() + */ + public static void reportMoreProduced() { + if (log.isDebugEnabled()) { + log.debug("More data produced than requested", + Exceptions.failWithOverflow()); + } + } + + /** + * Log a {@link Exceptions#duplicateOnSubscribeException() duplicate subscription} error. + * + * @see Exceptions#duplicateOnSubscribeException() + */ + public static void reportSubscriptionSet() { + if (log.isDebugEnabled()) { + log.debug("Duplicate Subscription has been detected", + Exceptions.duplicateOnSubscribeException()); + } + } + + /** + * Represents a fuseable Subscription that emits a single constant value synchronously + * to a Subscriber or consumer. + * + * @param subscriber the delegate {@link Subscriber} that will be requesting the value + * @param value the single value to be emitted + * @param the value type + * @return a new scalar {@link Subscription} + */ + public static Subscription scalarSubscription(CoreSubscriber subscriber, + T value){ + return new ScalarSubscription<>(subscriber, value); + } + + /** + * Represents a fuseable Subscription that emits a single constant value synchronously + * to a Subscriber or consumer. Also give the subscription a user-defined {@code stepName} + * for the purpose of {@link Scannable#stepName()}. + * + * @param subscriber the delegate {@link Subscriber} that will be requesting the value + * @param value the single value to be emitted + * @param stepName the {@link String} to represent the {@link Subscription} in {@link Scannable#stepName()} + * @param the value type + * @return a new scalar {@link Subscription} + */ + public static Subscription scalarSubscription(CoreSubscriber subscriber, + T value, String stepName){ + return new ScalarSubscription<>(subscriber, value, stepName); + } + + /** + * Safely gate a {@link Subscriber} by making sure onNext signals are delivered + * sequentially (serialized). + * Serialization uses thread-stealing and a potentially unbounded queue that might + * starve a calling thread if races are too important and {@link Subscriber} is slower. + * + *

+ * + * + * @param the relayed type + * @param subscriber the subscriber to serialize + * @return a serializing {@link Subscriber} + */ + public static CoreSubscriber serialize(CoreSubscriber subscriber) { + return new SerializedSubscriber<>(subscriber); + } + + /** + * A generic utility to atomically replace a subscription or cancel the replacement + * if current subscription is marked as cancelled (as in {@link #cancelledSubscription()}) + * or was concurrently updated before. + *

+ * The replaced subscription is itself cancelled. + * + * @param field The Atomic container + * @param instance the instance reference + * @param s the subscription + * @param the instance type + * + * @return true if replaced + */ + public static boolean set(AtomicReferenceFieldUpdater field, + F instance, + Subscription s) { + for (; ; ) { + Subscription a = field.get(instance); + if (a == CancelledSubscription.INSTANCE) { + s.cancel(); + return false; + } + if (field.compareAndSet(instance, a, s)) { + if (a != null) { + a.cancel(); + } + return true; + } + } + } + + /** + * Sets the given subscription once and returns true if successful, false + * if the field has a subscription already or has been cancelled. + *

+ * If the field already has a subscription, it is cancelled and the duplicate + * subscription is reported (see {@link #reportSubscriptionSet()}). + * + * @param the instance type containing the field + * @param field the field accessor + * @param instance the parent instance + * @param s the subscription to set once + * @return true if successful, false if the target was not empty or has been cancelled + */ + public static boolean setOnce(AtomicReferenceFieldUpdater field, F instance, Subscription s) { + Objects.requireNonNull(s, "subscription"); + Subscription a = field.get(instance); + if (a == CancelledSubscription.INSTANCE) { + s.cancel(); + return false; + } + if (a != null) { + s.cancel(); + reportSubscriptionSet(); + return false; + } + + if (field.compareAndSet(instance, null, s)) { + return true; + } + + a = field.get(instance); + + if (a == CancelledSubscription.INSTANCE) { + s.cancel(); + return false; + } + + s.cancel(); + reportSubscriptionSet(); + return false; + } + + /** + * Cap a subtraction to 0 + * + * @param a left operand + * @param b right operand + * @return Subtraction result or 0 if overflow + */ + public static long subOrZero(long a, long b) { + long res = a - b; + if (res < 0L) { + return 0; + } + return res; + } + + /** + * Atomically terminates the subscription if it is not already a + * {@link #cancelledSubscription()}, cancelling the subscription and setting the field + * to the singleton {@link #cancelledSubscription()}. + * + * @param the instance type containing the field + * @param field the field accessor + * @param instance the parent instance + * @return true if terminated, false if the subscription was already terminated + */ + public static boolean terminate(AtomicReferenceFieldUpdater field, + F instance) { + Subscription a = field.get(instance); + if (a != CancelledSubscription.INSTANCE) { + a = field.getAndSet(instance, CancelledSubscription.INSTANCE); + if (a != null && a != CancelledSubscription.INSTANCE) { + a.cancel(); + return true; + } + } + return false; + } + + /** + * Check Subscription current state and cancel new Subscription if current is set, + * or return true if ready to subscribe. + * + * @param current current Subscription, expected to be null + * @param next new Subscription + * @return true if Subscription can be used + */ + public static boolean validate(@Nullable Subscription current, Subscription next) { + Objects.requireNonNull(next, "Subscription cannot be null"); + if (current != null) { + next.cancel(); + //reportSubscriptionSet(); + return false; + } + + return true; + } + + /** + * Evaluate if a request is strictly positive otherwise {@link #reportBadRequest(long)} + * @param n the request value + * @return true if valid + */ + public static boolean validate(long n) { + if (n <= 0) { + reportBadRequest(n); + return false; + } + return true; + } + + /** + * If the actual {@link Subscriber} is not a {@link CoreSubscriber}, it will apply + * safe strict wrapping to apply all reactive streams rules including the ones + * relaxed by internal operators based on {@link CoreSubscriber}. + * + * @param passed subscriber type + * + * @param actual the {@link Subscriber} to apply hook on + * @return an eventually transformed {@link Subscriber} + */ + @SuppressWarnings("unchecked") + public static CoreSubscriber toCoreSubscriber(Subscriber actual) { + + Objects.requireNonNull(actual, "actual"); + + CoreSubscriber _actual; + + if (actual instanceof CoreSubscriber){ + _actual = (CoreSubscriber) actual; + } + else { + _actual = new StrictSubscriber<>(actual); + } + + return _actual; + } + + /** + * If the actual {@link CoreSubscriber} is not {@link reactor.core.Fuseable.ConditionalSubscriber}, + * it will apply an adapter which directly maps all + * {@link reactor.core.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} + */ + @SuppressWarnings("unchecked") + public static Fuseable.ConditionalSubscriber toConditionalSubscriber(CoreSubscriber actual) { + Objects.requireNonNull(actual, "actual"); + + Fuseable.ConditionalSubscriber _actual; + + if (actual instanceof Fuseable.ConditionalSubscriber) { + _actual = (Fuseable.ConditionalSubscriber) actual; + } + else { + _actual = new ConditionalSubscriberAdapter<>(actual); + } + + return _actual; + } + + + + static Context multiSubscribersContext(InnerProducer[] multicastInners){ + if (multicastInners.length > 0){ + return multicastInners[0].actual().currentContext(); + } + return Context.empty(); + } + + /** + * Add the amount {@code n} to the given field, capped to {@link Long#MAX_VALUE}, + * unless the field is already at {@link Long#MAX_VALUE} OR {@link Long#MIN_VALUE}. + * Return the value before the update. + * + * @param updater the field to update + * @param instance the instance bearing the field + * @param n the value to add + * @param the type of the field-bearing instance + * + * @return the old value of the field, before update. + */ + static long addCapCancellable(AtomicLongFieldUpdater updater, T instance, + long n) { + for (; ; ) { + long r = updater.get(instance); + if (r == Long.MIN_VALUE || r == Long.MAX_VALUE) { + return r; + } + long u = addCap(r, n); + if (updater.compareAndSet(instance, r, u)) { + return r; + } + } + } + + /** + * An unexpected exception is about to be dropped from an operator that has multiple + * subscribers (and thus potentially multiple Context with local onErrorDropped handlers). + * + * @param e the dropped exception + * @param multicastInners the inner targets of the multicast + * @see #onErrorDropped(Throwable, Context) + */ + static void onErrorDroppedMulticast(Throwable e, InnerProducer[] multicastInners) { + //TODO let this method go through multiple contexts and use their local handlers + //if at least one has no local handler, also call onErrorDropped(e, Context.empty()) + onErrorDropped(e, multiSubscribersContext(multicastInners)); + } + + /** + * An unexpected event is about to be dropped from an operator that has multiple + * subscribers (and thus potentially multiple Context with local onNextDropped handlers). + *

+ * If no hook is registered for {@link Hooks#onNextDropped(Consumer)}, the dropped + * element is just logged at DEBUG level. + * + * @param the dropped value type + * @param t the dropped data + * @param multicastInners the inner targets of the multicast + * @see #onNextDropped(Object, Context) + */ + static void onNextDroppedMulticast(T t, InnerProducer[] multicastInners) { + //TODO let this method go through multiple contexts and use their local handlers + //if at least one has no local handler, also call onNextDropped(t, Context.empty()) + onNextDropped(t, multiSubscribersContext(multicastInners)); + } + + static long producedCancellable(AtomicLongFieldUpdater updater, T instance, long n) { + for (; ; ) { + long current = updater.get(instance); + if (current == Long.MIN_VALUE) { + return Long.MIN_VALUE; + } + if (current == Long.MAX_VALUE) { + return Long.MAX_VALUE; + } + long update = current - n; + if (update < 0L) { + reportBadRequest(update); + update = 0L; + } + if (updater.compareAndSet(instance, current, update)) { + return update; + } + } + } + + static long unboundedOrPrefetch(int prefetch) { + return prefetch == Integer.MAX_VALUE ? Long.MAX_VALUE : prefetch; + } + + static int unboundedOrLimit(int prefetch) { + return prefetch == Integer.MAX_VALUE ? Integer.MAX_VALUE : (prefetch - (prefetch >> 2)); + } + + static int unboundedOrLimit(int prefetch, int lowTide) { + if (lowTide <= 0) { + return prefetch; + } + if (lowTide >= prefetch) { + return unboundedOrLimit(prefetch); + } + return prefetch == Integer.MAX_VALUE ? Integer.MAX_VALUE : lowTide; + } + + Operators() { + } + + static final class CorePublisherAdapter implements CorePublisher, + OptimizableOperator { + + final Publisher publisher; + + @Nullable + final OptimizableOperator optimizableOperator; + + CorePublisherAdapter(Publisher publisher) { + this.publisher = publisher; + if (publisher instanceof OptimizableOperator) { + @SuppressWarnings("unchecked") + OptimizableOperator optimSource = (OptimizableOperator) publisher; + this.optimizableOperator = optimSource; + } + else { + this.optimizableOperator = null; + } + } + + @Override + public void subscribe(CoreSubscriber subscriber) { + publisher.subscribe(subscriber); + } + + @Override + public void subscribe(Subscriber s) { + publisher.subscribe(s); + } + + @Override + public CoreSubscriber subscribeOrReturn(CoreSubscriber actual) { + return actual; + } + + @Override + public final CorePublisher source() { + return this; + } + + @Override + public final OptimizableOperator nextOptimizableSource() { + return optimizableOperator; + } + } + + static final Fuseable.ConditionalSubscriber EMPTY_SUBSCRIBER = new Fuseable.ConditionalSubscriber() { + @Override + public void onSubscribe(Subscription s) { + Throwable e = new IllegalStateException("onSubscribe should not be used"); + log.error("Unexpected call to Operators.emptySubscriber()", e); + } + + @Override + public void onNext(Object o) { + Throwable e = new IllegalStateException("onNext should not be used, got " + o); + log.error("Unexpected call to Operators.emptySubscriber()", e); + } + + @Override + public boolean tryOnNext(Object o) { + Throwable e = new IllegalStateException("tryOnNext should not be used, got " + o); + log.error("Unexpected call to Operators.emptySubscriber()", e); + return false; + } + + @Override + public void onError(Throwable t) { + Throwable e = new IllegalStateException("onError should not be used", t); + log.error("Unexpected call to Operators.emptySubscriber()", e); + } + + @Override + public void onComplete() { + Throwable e = new IllegalStateException("onComplete should not be used"); + log.error("Unexpected call to Operators.emptySubscriber()", e); + } + + @Override + public Context currentContext() { + return Context.empty(); + } + }; + // + + final static class CancelledSubscription implements Subscription, Scannable { + static final CancelledSubscription INSTANCE = new CancelledSubscription(); + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) { + return true; + } + return null; + } + + @Override + public void cancel() { + // deliberately no op + } + + @Override + public void request(long n) { + // deliberately no op + } + + @Override + public String stepName() { + return "cancelledSubscription"; + } + } + + final static class EmptySubscription implements QueueSubscription, Scannable { + static final EmptySubscription INSTANCE = new EmptySubscription(); + static final EmptySubscription FROM_SUBSCRIBE_INSTANCE = new EmptySubscription(); + + @Override + public void cancel() { + // deliberately no op + } + + @Override + public void clear() { + // deliberately no op + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + @Nullable + public Object poll() { + return null; + } + + @Override + public void request(long n) { + // deliberately no op + } + + @Override + public int requestFusion(int requestedMode) { + return NONE; // can't enable fusion due to complete/error possibility + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return true; + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public String stepName() { + return "emptySubscription"; + } + } + + /** + * Base class for Subscribers that will receive their Subscriptions at any time, yet + * they might also need to be cancelled or requested at any time. + */ + public static class DeferredSubscription + implements Subscription, Scannable { + + static final int STATE_CANCELLED = -2; + static final int STATE_SUBSCRIBED = -1; + + Subscription s; + volatile long requested; + + protected boolean isCancelled(){ + return requested == STATE_CANCELLED; + } + + @Override + public void cancel() { + final long state = REQUESTED.getAndSet(this, STATE_CANCELLED); + if (state == STATE_CANCELLED) { + return; + } + + if (state == STATE_SUBSCRIBED) { + this.s.cancel(); + } + } + + protected void terminate() { + REQUESTED.getAndSet(this, STATE_CANCELLED); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + long requested = this.requested; // volatile read to see subscription + if (key == Attr.PARENT) return s; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested < 0 ? 0 : requested; + if (key == Attr.CANCELLED) return isCancelled(); + + return null; + } + + @Override + public void request(long n) { + long r = this.requested; // volatile read beforehand + + if (r > STATE_SUBSCRIBED) { // works only in case onSubscribe has not happened + long u; + for (;;) { // normal CAS loop with overflow protection + if (r == Long.MAX_VALUE) { // if r == Long.MAX_VALUE then we dont care and we can loose this request just in case of racing + return; + } + u = Operators.addCap(r, n); + if (REQUESTED.compareAndSet(this, r, u)) { // Means increment happened before onSubscribe + return; + } + else { // Means increment happened after onSubscribe + r = this.requested; // update new state to see what exactly happened (onSubscribe | cancel | requestN) + + if (r < 0) { // check state (expect -1 | -2 to exit, otherwise repeat) + break; + } + } + } + } + + if (r == STATE_CANCELLED) { // if canceled, just exit + return; + } + + this.s.request(n); // if onSubscribe -> subscription exists (and we sure of that because volatile read after volatile write) so we can execute requestN on the subscription + } + + /** + * Atomically sets the single subscription and requests the missed amount from it. + * + * @param s the subscription to set + * @return false if this arbiter is cancelled or there was a subscription already set + */ + public final boolean set(Subscription s) { + Objects.requireNonNull(s, "s"); + final long state = this.requested; + Subscription a = this.s; + if (state == STATE_CANCELLED) { + s.cancel(); + return false; + } + if (a != null) { + s.cancel(); + reportSubscriptionSet(); + return false; + } + + long r; + long accumulated = 0; + for (;;) { + r = this.requested; + + if (r == STATE_CANCELLED || r == STATE_SUBSCRIBED) { + s.cancel(); + return false; + } + + this.s = s; + + long toRequest = r - accumulated; + if (toRequest > 0) { // if there is something, + s.request(toRequest); // then we do a request on the given subscription + } + accumulated += toRequest; + + if (REQUESTED.compareAndSet(this, r, STATE_SUBSCRIBED)) { + return true; + } + } + } + + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(DeferredSubscription.class, "requested"); + + } + + /** + * 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 + QueueSubscription { + + protected final CoreSubscriber actual; + + /** + * The value stored by this Mono operator. Strongly prefer using {@link #setValue(Object)} + * rather than direct writes to this field, when possible. + */ + @Nullable + protected O value; + volatile int state; //see STATE field updater + + public MonoSubscriber(CoreSubscriber actual) { + this.actual = actual; + } + + @Override + public void cancel() { + O v = value; + value = null; + STATE.set(this, CANCELLED); + discard(v); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return isCancelled(); + if (key == Attr.TERMINATED) return state == HAS_REQUEST_HAS_VALUE || state == NO_REQUEST_HAS_VALUE; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public final void clear() { + STATE.lazySet(this, FUSED_CONSUMED); + this.value = null; + } + + /** + * Tries to emit the value and complete the underlying subscriber or + * stores the value away until there is a request for it. + *

+ * Make sure this method is called at most once + * @param v the value to emit + */ + 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) { + this.value = null; + discard(v); + return; + } + + if (state == HAS_REQUEST_NO_VALUE && STATE.compareAndSet(this, HAS_REQUEST_NO_VALUE, HAS_REQUEST_HAS_VALUE)) { + this.value = null; + Subscriber a = actual; + a.onNext(v); + a.onComplete(); + return; + } + setValue(v); + if (state == NO_REQUEST_NO_VALUE && STATE.compareAndSet(this, NO_REQUEST_NO_VALUE, NO_REQUEST_HAS_VALUE)) { + return; + } + } + } + + /** + * Discard the given value, generally this.value field. Lets derived subscriber with further knowledge about + * the possible types of the value discard such values in a specific way. Note that fields should generally be + * nulled out along the discard call. + * + * @param v the value to discard + */ + protected void discard(@Nullable O v) { + Operators.onDiscard(v, actual.currentContext()); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + /** + * Returns true if this Subscription has been cancelled. + * @return true if this Subscription has been cancelled + */ + public final boolean isCancelled() { + return state == CANCELLED; + } + + @Override + public final boolean isEmpty() { + return this.state != FUSED_READY; + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + @SuppressWarnings("unchecked") + public void onNext(I t) { + setValue((O) t); + } + + @Override + public void onSubscribe(Subscription s) { + //if upstream + } + + @Override + @Nullable + public final O poll() { + if (STATE.compareAndSet(this, FUSED_READY, FUSED_CONSUMED)) { + O v = value; + value = null; + return v; + } + return null; + } + + @Override + public void request(long n) { + if (validate(n)) { + for (; ; ) { + int s = state; + 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 ((s & ~NO_REQUEST_HAS_VALUE) != 0) { + return; + } + if (s == NO_REQUEST_HAS_VALUE && STATE.compareAndSet(this, NO_REQUEST_HAS_VALUE, HAS_REQUEST_HAS_VALUE)) { + O v = value; + if (v != null) { + value = null; + Subscriber a = actual; + a.onNext(v); + a.onComplete(); + } + return; + } + if (STATE.compareAndSet(this, NO_REQUEST_NO_VALUE, HAS_REQUEST_NO_VALUE)) { + return; + } + } + } + } + + @Override + public int requestFusion(int mode) { + if ((mode & ASYNC) != 0) { + STATE.lazySet(this, FUSED_EMPTY); + return ASYNC; + } + return NONE; + } + + /** + * Set the value internally, without impacting request tracking state. + * This however discards the provided value when detecting a cancellation. + * + * @param value the new value. + * @see #complete(Object) + */ + public void setValue(@Nullable O value) { + if (STATE.get(this) == CANCELLED) { + discard(value); + return; + } + this.value = value; + } + + @Override + public int size() { + return isEmpty() ? 0 : 1; + } + + /** + * Indicates this Subscription has no value and not requested yet. + */ + static final int NO_REQUEST_NO_VALUE = 0; + /** + * Indicates this Subscription has a value but not requested yet. + */ + static final int NO_REQUEST_HAS_VALUE = 1; + /** + * Indicates this Subscription has been requested but there is no value yet. + */ + static final int HAS_REQUEST_NO_VALUE = 2; + /** + * Indicates this Subscription has both request and value. + */ + static final int HAS_REQUEST_HAS_VALUE = 3; + /** + * 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; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(MonoSubscriber.class, "state"); + } + + + /** + * A subscription implementation that arbitrates request amounts between subsequent Subscriptions, including the + * duration until the first Subscription is set. + *

+ * The class is thread safe but switching Subscriptions should happen only when the source associated with the current + * Subscription has finished emitting values. Otherwise, two sources may emit for one request. + *

+ * You should call {@link #produced(long)} or {@link #producedOne()} after each element has been delivered to properly + * account the outstanding request amount in case a Subscription switch happens. + * + * @param the input value type + * @param the output value type + */ + abstract static class MultiSubscriptionSubscriber + implements InnerOperator { + + final CoreSubscriber actual; + + protected boolean unbounded; + /** + * The current subscription which may null if no Subscriptions have been set. + */ + Subscription subscription; + /** + * The current outstanding request amount. + */ + long requested; + volatile Subscription missedSubscription; + volatile long missedRequested; + volatile long missedProduced; + volatile int wip; + volatile boolean cancelled; + + public MultiSubscriptionSubscriber(CoreSubscriber actual) { + this.actual = actual; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + drain(); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) + return missedSubscription != null ? missedSubscription : subscription; + if (key == Attr.CANCELLED) return isCancelled(); + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) + return Operators.addCap(requested, missedRequested); + + return InnerOperator.super.scanUnsafe(key); + } + + public final boolean isUnbounded() { + return unbounded; + } + + final boolean isCancelled() { + return cancelled; + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onSubscribe(Subscription s) { + set(s); + } + + public final void produced(long n) { + if (unbounded) { + return; + } + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + long r = requested; + + if (r != Long.MAX_VALUE) { + long u = r - n; + if (u < 0L) { + reportMoreProduced(); + u = 0; + } + requested = u; + } else { + unbounded = true; + } + + if (WIP.decrementAndGet(this) == 0) { + return; + } + + drainLoop(); + + return; + } + + addCap(MISSED_PRODUCED, this, n); + + drain(); + } + + final void producedOne() { + if (unbounded) { + return; + } + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + long r = requested; + + if (r != Long.MAX_VALUE) { + r--; + if (r < 0L) { + reportMoreProduced(); + r = 0; + } + requested = r; + } else { + unbounded = true; + } + + if (WIP.decrementAndGet(this) == 0) { + return; + } + + drainLoop(); + + return; + } + + addCap(MISSED_PRODUCED, this, 1L); + + drain(); + } + + @Override + public final void request(long n) { + if (validate(n)) { + if (unbounded) { + return; + } + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + long r = requested; + + if (r != Long.MAX_VALUE) { + r = addCap(r, n); + requested = r; + if (r == Long.MAX_VALUE) { + unbounded = true; + } + } + Subscription a = subscription; + + if (WIP.decrementAndGet(this) != 0) { + drainLoop(); + } + + if (a != null) { + a.request(n); + } + + return; + } + + addCap(MISSED_REQUESTED, this, n); + + drain(); + } + } + + public final void set(Subscription s) { + if (cancelled) { + s.cancel(); + return; + } + + Objects.requireNonNull(s); + + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + Subscription a = subscription; + + if (a != null && shouldCancelCurrent()) { + a.cancel(); + } + + subscription = s; + + long r = requested; + + if (WIP.decrementAndGet(this) != 0) { + drainLoop(); + } + + if (r != 0L) { + s.request(r); + } + + return; + } + + Subscription a = MISSED_SUBSCRIPTION.getAndSet(this, s); + if (a != null && shouldCancelCurrent()) { + a.cancel(); + } + drain(); + } + + /** + * When setting a new subscription via set(), should + * the previous subscription be cancelled? + * @return true if cancellation is needed + */ + protected boolean shouldCancelCurrent() { + return false; + } + + final void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + drainLoop(); + } + + final void drainLoop() { + int missed = 1; + + long requestAmount = 0L; + long alreadyInRequestAmount = 0L; + Subscription requestTarget = null; + + for (; ; ) { + + Subscription ms = missedSubscription; + + if (ms != null) { + ms = MISSED_SUBSCRIPTION.getAndSet(this, null); + } + + long mr = missedRequested; + if (mr != 0L) { + mr = MISSED_REQUESTED.getAndSet(this, 0L); + } + + long mp = missedProduced; + if (mp != 0L) { + mp = MISSED_PRODUCED.getAndSet(this, 0L); + } + + Subscription a = subscription; + + if (cancelled) { + if (a != null) { + a.cancel(); + subscription = null; + } + if (ms != null) { + ms.cancel(); + } + } else { + long r = requested; + if (r != Long.MAX_VALUE) { + long u = addCap(r, mr); + + if (u != Long.MAX_VALUE) { + long v = u - mp; + if (v < 0L) { + reportMoreProduced(); + v = 0; + } + r = v; + } else { + r = u; + } + requested = r; + } + + if (ms != null) { + if (a != null && shouldCancelCurrent()) { + a.cancel(); + } + subscription = ms; + if (r != 0L) { + requestAmount = addCap(requestAmount, r - alreadyInRequestAmount); + requestTarget = ms; + } + } else if (mr != 0L && a != null) { + requestAmount = addCap(requestAmount, mr); + alreadyInRequestAmount += mr; + requestTarget = a; + } + } + + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + if (requestAmount != 0L) { + requestTarget.request(requestAmount); + } + return; + } + } + } + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + MISSED_SUBSCRIPTION = + AtomicReferenceFieldUpdater.newUpdater(MultiSubscriptionSubscriber.class, + Subscription.class, + "missedSubscription"); + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater + MISSED_REQUESTED = + AtomicLongFieldUpdater.newUpdater(MultiSubscriptionSubscriber.class, "missedRequested"); + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater MISSED_PRODUCED = + AtomicLongFieldUpdater.newUpdater(MultiSubscriptionSubscriber.class, "missedProduced"); + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(MultiSubscriptionSubscriber.class, "wip"); + } + + /** + * Represents a fuseable Subscription that emits a single constant value synchronously + * to a Subscriber or consumer. + * + * @param the value type + */ + static final class ScalarSubscription + implements Fuseable.SynchronousSubscription, InnerProducer { + + final CoreSubscriber actual; + + final T value; + + @Nullable + final String stepName; + + volatile int once; + + ScalarSubscription(CoreSubscriber actual, T value) { + this(actual, value, null); + } + + ScalarSubscription(CoreSubscriber actual, T value, String stepName) { + this.value = Objects.requireNonNull(value, "value"); + this.actual = Objects.requireNonNull(actual, "actual"); + this.stepName = stepName; + } + + @Override + public void cancel() { + if (once == 0) { + Operators.onDiscard(value, actual.currentContext()); + } + ONCE.lazySet(this, 2); + } + + @Override + public void clear() { + if (once == 0) { + Operators.onDiscard(value, actual.currentContext()); + } + ONCE.lazySet(this, 1); + } + + @Override + public boolean isEmpty() { + return once != 0; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public T poll() { + if (once == 0) { + ONCE.lazySet(this, 1); + return value; + } + return null; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return once == 1; + if (key == Attr.CANCELLED) return once == 2; + if (key == Attr.RUN_STYLE) return RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + if (validate(n)) { + if (ONCE.compareAndSet(this, 0, 1)) { + Subscriber a = actual; + a.onNext(value); + if(once != 2) { + a.onComplete(); + } + } + } + } + + @Override + public int requestFusion(int requestedMode) { + if ((requestedMode & Fuseable.SYNC) != 0) { + return Fuseable.SYNC; + } + return 0; + } + + @Override + public int size() { + return isEmpty() ? 0 : 1; + } + + @Override + public String stepName() { + return stepName != null ? stepName : ("scalarSubscription(" + value + ")"); + } + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(ScalarSubscription.class, "once"); + } + + final static class DrainSubscriber implements CoreSubscriber { + + static final DrainSubscriber INSTANCE = new DrainSubscriber(); + + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(Object o) { + + } + + @Override + public void onError(Throwable t) { + Operators.onErrorDropped(Exceptions.errorCallbackNotImplemented(t), Context.empty()); + } + + @Override + public void onComplete() { + + } + + @Override + public Context currentContext() { + return Context.empty(); + } + } + + /** + * This class wraps any non-conditional {@link CoreSubscriber} so the delegate + * can have an emulation of {@link reactor.core.Fuseable.ConditionalSubscriber} + * behaviors + * + * @param passed subscriber type + */ + final static class ConditionalSubscriberAdapter implements Fuseable.ConditionalSubscriber { + + final CoreSubscriber delegate; + + ConditionalSubscriberAdapter(CoreSubscriber delegate) { + this.delegate = delegate; + } + + @Override + public Context currentContext() { + return delegate.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + delegate.onSubscribe(s); + } + + @Override + public void onNext(T t) { + delegate.onNext(t); + } + + @Override + public void onError(Throwable t) { + delegate.onError(t); + } + + @Override + public void onComplete() { + delegate.onComplete(); + } + + @Override + public boolean tryOnNext(T t) { + delegate.onNext(t); + return true; + } + } + + final static class LiftFunction + implements Function, Publisher> { + + final Predicate filter; + final String name; + + final BiFunction, + ? extends CoreSubscriber> lifter; + + static final LiftFunction liftScannable( + @Nullable Predicate filter, + BiFunction, ? extends CoreSubscriber> lifter) { + Objects.requireNonNull(lifter, "lifter"); + + Predicate effectiveFilter = null; + if (filter != null) { + effectiveFilter = pub -> filter.test(Scannable.from(pub)); + } + + BiFunction, ? extends CoreSubscriber> + effectiveLifter = (pub, sub) -> lifter.apply(Scannable.from(pub), sub); + + return new LiftFunction<>(effectiveFilter, effectiveLifter, lifter.toString()); + } + + static final LiftFunction liftPublisher( + @Nullable Predicate filter, + BiFunction, ? extends CoreSubscriber> lifter) { + Objects.requireNonNull(lifter, "lifter"); + return new LiftFunction<>(filter, lifter, lifter.toString()); + } + + private LiftFunction(@Nullable Predicate filter, + BiFunction, ? extends CoreSubscriber> lifter, + String name) { + this.filter = filter; + this.lifter = Objects.requireNonNull(lifter, "lifter"); + this.name = Objects.requireNonNull(name, "name"); + } + + @Override + @SuppressWarnings("unchecked") + public Publisher apply(Publisher publisher) { + if (filter != null && !filter.test(publisher)) { + return (Publisher)publisher; + } + + if (publisher instanceof Fuseable) { + if (publisher instanceof Mono) { + return new MonoLiftFuseable<>(publisher, this); + } + if (publisher instanceof ParallelFlux) { + return new ParallelLiftFuseable<>((ParallelFlux)publisher, this); + } + if (publisher instanceof ConnectableFlux) { + return new ConnectableLiftFuseable<>((ConnectableFlux) publisher, this); + } + if (publisher instanceof GroupedFlux) { + return new GroupedLiftFuseable<>((GroupedFlux) publisher, this); + } + return new FluxLiftFuseable<>(publisher, this); + } + else { + if (publisher instanceof Mono) { + return new MonoLift<>(publisher, this); + } + if (publisher instanceof ParallelFlux) { + return new ParallelLift<>((ParallelFlux)publisher, this); + } + if (publisher instanceof ConnectableFlux) { + return new ConnectableLift<>((ConnectableFlux) publisher, this); + } + if (publisher instanceof GroupedFlux) { + return new GroupedLift<>((GroupedFlux) publisher, this); + } + return new FluxLift<>(publisher, this); + } + } + } + + /*package*/ static class MonoInnerProducerBase + implements InnerProducer { + + private final CoreSubscriber actual; + + /** + * The value stored by this Mono operator. + */ + private O value; + + private volatile int state; //see STATE field updater + @SuppressWarnings("rawtypes") + private static final AtomicIntegerFieldUpdater STATE = + AtomicIntegerFieldUpdater.newUpdater(MonoInnerProducerBase.class, "state"); + + public MonoInnerProducerBase(CoreSubscriber actual) { + this.actual = actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return isCancelled(); + if (key == Attr.TERMINATED) return hasCompleted(state); + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + return InnerProducer.super.scanUnsafe(key); + } + + /** + * Tries to emit the provided value and complete the underlying subscriber or + * stores the value away until there is a request for it. + *

+ * Make sure this method is called at most once. Can't be used in addition to {@link #complete()}. + * @param v the value to emit + */ + public final void complete(O v) { + for (; ; ) { + int s = this.state; + if (isCancelled(s)) { + discard(v); + return; + } + + if (hasRequest(s) && STATE.compareAndSet(this, s, s | (HAS_VALUE | HAS_COMPLETED))) { + this.value = null; + + doOnComplete(v); + + actual.onNext(v); + actual.onComplete(); + return; + } + + this.value = v; + if ( /*!hasRequest(s) && */ STATE.compareAndSet(this, s, s | (HAS_VALUE | HAS_COMPLETED))) { + return; + } + } + } + + /** + * Tries to emit the {@link #value} stored if any, and complete the underlying subscriber. + *

+ * Make sure this method is called at most once. Can't be used in addition to {@link #complete(Object)}. + */ + public final void complete() { + for (; ; ) { + int s = this.state; + if (isCancelled(s)) { + return; + } + if (STATE.compareAndSet(this, s, s | HAS_COMPLETED)) { + if (hasValue(s) && hasRequest(s)) { + O v = this.value; + this.value = null; // aggressively null value to prevent strong ref after complete + + doOnComplete(v); + + actual.onNext(v); + actual.onComplete(); + return; + } + if (!hasValue(s)) { + actual.onComplete(); + return; + } + if (!hasRequest(s)) { + return; + } + } + } + } + + /** + * Hook for subclasses when the actual completion appears + * + * @param v the value passed to {@code onComplete(Object)} + */ + protected void doOnComplete(O v) { + + } + + /** + * Discard the given value, if the value to discard is not the one held by this instance + * (see {@link #discardTheValue()} for that purpose. Lets derived subscriber with further knowledge about + * the possible types of the value discard such values in a specific way. Note that fields should generally be + * nulled out along the discard call. + * + * @param v the value to discard + */ + protected final void discard(@Nullable O v) { + Operators.onDiscard(v, actual.currentContext()); + } + + protected final void discardTheValue() { + discard(this.value); + this.value = null; // aggressively null value to prevent strong ref after complete + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + /** + * Returns true if this Subscription has been cancelled. + * @return true if this Subscription has been cancelled + */ + public final boolean isCancelled() { + return state == CANCELLED; + } + + + @Override + public void request(long n) { + if (validate(n)) { + for (; ; ) { + int s = state; + if (isCancelled(s)) { + return; + } + if (hasRequest(s)) { + return; + } + if (STATE.compareAndSet(this, s, s | HAS_REQUEST)) { + doOnRequest(n); + if (hasValue(s) && hasCompleted(s)) { + O v = this.value; + this.value = null; // aggressively null value to prevent strong ref after complete + + doOnComplete(v); + + actual.onNext(v); + actual.onComplete(); + } + return; + } + } + } + } + + /** + * Hook for subclasses on the first request successfully marked on the state + * @param n the value passed to {@code request(long)} + */ + protected void doOnRequest(long n) { + + } + + /** + * Set the value internally, without impacting request tracking state. + * This however discards the provided value when detecting a cancellation. + * + * @param value the new value. + * @see #complete(Object) + */ + protected final void setValue(@Nullable O value) { + this.value = value; + for (; ; ) { + int s = this.state; + if (isCancelled(s)) { + discardTheValue(); + return; + } + if (STATE.compareAndSet(this, s, s | HAS_VALUE)) { + return; + } + } + } + + @Override + public final void cancel() { + int previous = STATE.getAndSet(this, CANCELLED); + + if (isCancelled(previous)) { + return; + } + + doOnCancel(); + + if (hasValue(previous) // Had a value... + && (previous & (HAS_COMPLETED|HAS_REQUEST)) != (HAS_COMPLETED|HAS_REQUEST) // ... but did not use it + ) { + discardTheValue(); + } + } + + /** + * Hook for subclasses, called as the first operation when {@link #cancel()} is called. Default + * implementation is a no-op. + */ + protected void doOnCancel() { + + } + + // The following are to be used as bit masks, not as values per se. + private static final int HAS_VALUE = 0b00000001; + private static final int HAS_REQUEST = 0b00000010; + private static final int HAS_COMPLETED = 0b00000100; + // The following are to be used as value (ie using == or !=). + private static final int CANCELLED = 0b10000000; + + private static boolean isCancelled(int s) { + return s == CANCELLED; + } + + private static boolean hasRequest(int s) { + return (s & HAS_REQUEST) == HAS_REQUEST; + } + + private static boolean hasValue(int s) { + return (s & HAS_VALUE) == HAS_VALUE; + } + + private static boolean hasCompleted(int s) { + return (s & HAS_COMPLETED) == HAS_COMPLETED; + } + + } + + + final static Logger log = Loggers.getLogger(Operators.class); +} Index: 3rdParty_sources/reactor/reactor/core/publisher/OptimizableOperator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/OptimizableOperator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/OptimizableOperator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016-2021 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.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.util.annotation.Nullable; + +/** + * A common interface of operator in Reactor which adds contracts around subscription + * optimizations: + *

    + *
  • looping instead of recursive subscribes, via {@link #subscribeOrReturn(CoreSubscriber)} and + * {@link #nextOptimizableSource()}
  • + *
+ * + * @param the {@link CoreSubscriber} data type + * @since 3.3.0 + */ +interface OptimizableOperator extends CorePublisher { + + /** + * Allow delegation of the subscription by returning a {@link CoreSubscriber}, or force + * subscription encapsulation by returning null. This can be used in conjunction with {@link #nextOptimizableSource()} + * to perform subscription in a loop instead of by recursion. + * + * @return next {@link CoreSubscriber} or "null" if the subscription was already done inside the method + */ + @Nullable + CoreSubscriber subscribeOrReturn(CoreSubscriber actual) throws Throwable; + + /** + * @return {@link CorePublisher} to call {@link CorePublisher#subscribe(CoreSubscriber)} on + * if {@link #nextOptimizableSource()} have returned null result + */ + CorePublisher source(); + + /** + * Allow delegation of the subscription by returning the next {@link OptimizableOperator} UP in the + * chain, to be subscribed to next. This method MUST return a non-null value if the {@link #subscribeOrReturn(CoreSubscriber)} + * method returned a non null value. In that case, this next operator can be used in conjunction with {@link #subscribeOrReturn(CoreSubscriber)} + * to perform subscription in a loop instead of by recursion. + * + * @return next {@link OptimizableOperator} if {@link #subscribeOrReturn(CoreSubscriber)} have returned non-null result + */ + @Nullable + OptimizableOperator nextOptimizableSource(); +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelArraySource.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelArraySource.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelArraySource.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015-2021 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.Publisher; +import reactor.core.CoreSubscriber; + +/** + * Wraps multiple Publishers into a ParallelFlux which runs them + * in parallel. + * + * @param the value type + */ +final class ParallelArraySource extends ParallelFlux implements SourceProducer { + final Publisher[] sources; + + ParallelArraySource(Publisher[] sources) { + //noinspection ConstantConditions + if (sources == null || sources.length == 0) { + throw new IllegalArgumentException("Zero publishers not supported"); + } + this.sources = sources; + } + + @Override + public int parallelism() { + return sources.length; + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + for (int i = 0; i < n; i++) { + Flux.from(sources[i]).subscribe(subscribers[i]); + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + return SourceProducer.super.scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelCollect.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelCollect.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelCollect.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2016-2021 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.BiConsumer; +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; +import reactor.util.annotation.Nullable; + +/** + * Reduce the sequence of values in each 'rail' to a single value. + * + * @param the input value type + * @param the collection type + */ +final class ParallelCollect extends ParallelFlux implements Scannable, Fuseable { + + final ParallelFlux source; + + final Supplier initialCollection; + + final BiConsumer collector; + + ParallelCollect(ParallelFlux source, + Supplier initialCollection, + BiConsumer collector) { + this.source = source; + this.initialCollection = initialCollection; + this.collector = collector; + } + + @Override + @Nullable + 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; + + return null; + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") CoreSubscriber[] parents = new CoreSubscriber[n]; + + for (int i = 0; i < n; i++) { + + C initialValue; + + try { + initialValue = Objects.requireNonNull(initialCollection.get(), + "The initialSupplier returned a null value"); + } + catch (Throwable ex) { + reportError(subscribers, Operators.onOperatorError(ex, + subscribers[i].currentContext())); + return; + } + + parents[i] = new ParallelCollectSubscriber<>(subscribers[i], + initialValue, + collector); + } + + source.subscribe(parents); + } + + void reportError(Subscriber[] subscribers, Throwable ex) { + for (Subscriber s : subscribers) { + Operators.error(s, ex); + } + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + static final class ParallelCollectSubscriber + extends Operators.MonoSubscriber { + + final BiConsumer collector; + + C collection; + + Subscription s; + + boolean done; + + ParallelCollectSubscriber(CoreSubscriber subscriber, + C initialValue, + BiConsumer collector) { + super(subscriber); + this.collection = initialValue; + this.collector = collector; + } + + @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); + } + catch (Throwable ex) { + onError(Operators.onOperatorError(this, ex, t, actual.currentContext())); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + collection = null; + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + C c = collection; + collection = null; + complete(c); + } + + @Override + public void cancel() { + super.cancel(); + s.cancel(); + } + + @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/ParallelConcatMap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelConcatMap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelConcatMap.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016-2021 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.function.Function; +import java.util.function.Supplier; + +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.core.publisher.FluxConcatMap.ErrorMode; +import reactor.util.annotation.Nullable; + +/** + * Concatenates the generated Publishers on each rail. + * + * @param the input value type + * @param the output value type + */ +final class ParallelConcatMap extends ParallelFlux implements Scannable{ + + final ParallelFlux source; + + final Function> mapper; + + final Supplier> queueSupplier; + + final int prefetch; + + final ErrorMode errorMode; + + ParallelConcatMap( + ParallelFlux source, + Function> mapper, + Supplier> queueSupplier, + int prefetch, ErrorMode errorMode) { + this.source = source; + this.mapper = Objects.requireNonNull(mapper, "mapper"); + this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); + this.prefetch = prefetch; + this.errorMode = Objects.requireNonNull(errorMode, "errorMode"); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.DELAY_ERROR) return errorMode != ErrorMode.IMMEDIATE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + @SuppressWarnings("unchecked") + CoreSubscriber[] parents = new CoreSubscriber[n]; + + for (int i = 0; i < n; i++) { + parents[i] = FluxConcatMap.subscriber(subscribers[i], mapper, + queueSupplier, prefetch, errorMode); + } + + source.subscribe(parents); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelDoOnEach.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelDoOnEach.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelDoOnEach.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2016-2021 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.BiConsumer; +import java.util.function.Consumer; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Execute a Consumer in each 'rail' for the current element passing through. + * + * @param the value type + */ +final class ParallelDoOnEach extends ParallelFlux implements Scannable { + + final ParallelFlux source; + + final BiConsumer onNext; + final BiConsumer onError; + final Consumer onComplete; + + ParallelDoOnEach( + ParallelFlux source, + @Nullable BiConsumer onNext, + @Nullable BiConsumer onError, + @Nullable Consumer onComplete + ) { + this.source = source; + + this.onNext = onNext; + this.onError = onError; + this.onComplete = onComplete; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + CoreSubscriber[] parents = new CoreSubscriber[n]; + + boolean conditional = subscribers[0] instanceof Fuseable.ConditionalSubscriber; + + for (int i = 0; i < n; i++) { + CoreSubscriber subscriber = subscribers[i]; + SignalPeek signalPeek = new DoOnEachSignalPeek(subscriber.currentContext()); + + if (conditional) { + parents[i] = new FluxPeekFuseable.PeekConditionalSubscriber<>( + (Fuseable.ConditionalSubscriber) subscriber, signalPeek); + } + else { + parents[i] = new FluxPeek.PeekSubscriber<>(subscriber, signalPeek); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + @Nullable + 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; + + return null; + } + + private class DoOnEachSignalPeek implements SignalPeek { + + Consumer onNextCall; + + Consumer onErrorCall; + + Runnable onCompleteCall; + + public DoOnEachSignalPeek(Context ctx) { + onNextCall = onNext != null ? v -> onNext.accept(ctx, v) : null; + onErrorCall = onError != null ? e -> onError.accept(ctx, e) : null; + onCompleteCall = onComplete != null ? () -> onComplete.accept(ctx) : null; + } + + @Override + public Consumer onSubscribeCall() { + return null; + } + + @Override + public Consumer onNextCall() { + return onNextCall; + } + + @Override + public Consumer onErrorCall() { + return onErrorCall; + } + + @Override + public Runnable onCompleteCall() { + return onCompleteCall; + } + + @Override + public Runnable onAfterTerminateCall() { + return null; + } + + @Override + public LongConsumer onRequestCall() { + return null; + } + + @Override + public Runnable onCancelCall() { + return null; + } + + @Override + public Object scanUnsafe(Attr key) { + return null; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelFilter.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelFilter.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelFilter.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016-2021 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.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Filters each 'rail' of the source ParallelFlux with a predicate function. + * + * @param the input value type + */ +final class ParallelFilter extends ParallelFlux implements Scannable{ + + final ParallelFlux source; + + final Predicate predicate; + + ParallelFilter(ParallelFlux source, Predicate predicate) { + this.source = source; + this.predicate = predicate; + } + + @Override + @Nullable + 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; + + return null; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + CoreSubscriber[] parents = new CoreSubscriber[n]; + + boolean conditional = subscribers[0] instanceof Fuseable.ConditionalSubscriber; + + for (int i = 0; i < n; i++) { + if (conditional) { + parents[i] = new FluxFilter.FilterConditionalSubscriber<>( + (Fuseable.ConditionalSubscriber)subscribers[i], predicate); + } + else { + parents[i] = new FluxFilter.FilterSubscriber<>(subscribers[i], predicate); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelFlatMap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelFlatMap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelFlatMap.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2016-2021 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.Queue; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.reactivestreams.Publisher; +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Flattens the generated Publishers on each rail. + * + * @param the input value type + * @param the output value type + */ +final class ParallelFlatMap extends ParallelFlux implements Scannable{ + + final ParallelFlux source; + + final Function> mapper; + + final boolean delayError; + + final int maxConcurrency; + + final Supplier> mainQueueSupplier; + + final int prefetch; + + final Supplier> innerQueueSupplier; + + ParallelFlatMap( + ParallelFlux source, + Function> mapper, + boolean delayError, + int maxConcurrency, Supplier> mainQueueSupplier, + int prefetch, Supplier> innerQueueSupplier) { + this.source = source; + this.mapper = mapper; + this.delayError = delayError; + this.maxConcurrency = maxConcurrency; + this.mainQueueSupplier = mainQueueSupplier; + this.prefetch = prefetch; + this.innerQueueSupplier = innerQueueSupplier; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.DELAY_ERROR) return delayError; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + @SuppressWarnings("unchecked") + CoreSubscriber[] parents = new CoreSubscriber[n]; + + for (int i = 0; i < n; i++) { + parents[i] = new FluxFlatMap.FlatMapMain<>(subscribers[i], + mapper, + delayError, + maxConcurrency, + mainQueueSupplier, + prefetch, + innerQueueSupplier); + } + + source.subscribe(parents); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelFlux.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelFlux.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelFlux.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,1336 @@ +/* + * Copyright (c) 2016-2021 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.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Queue; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.LongConsumer; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.logging.Level; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.CorePublisher; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.publisher.FluxConcatMap.ErrorMode; +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.Schedulers; +import reactor.util.Logger; +import reactor.util.annotation.Nullable; +import reactor.util.concurrent.Queues; +import reactor.util.context.Context; + +/** + * A ParallelFlux publishes to an array of Subscribers, in parallel 'rails' (or + * {@link #groups() 'groups'}). + *

+ * Use {@link #from} to start processing a regular Publisher in 'rails', which each + * cover a subset of the original Publisher's data. {@link Flux#parallel()} is a + * convenient shortcut to achieve that on a {@link Flux}. + *

+ * Use {@link #runOn} to introduce where each 'rail' should run on thread-wise. + *

+ * Use {@link #sequential} to merge the sources back into a single {@link Flux}. + *

+ * Use {@link #then} to listen for all rails termination in the produced {@link Mono} + *

+ * {@link #subscribe(Subscriber)} if you simply want to subscribe to the merged sequence. + * Note that other variants like {@link #subscribe(Consumer)} instead do multiple + * subscribes, one on each rail (which means that the lambdas should be as stateless and + * side-effect free as possible). + * + * + * @param the value type + */ +public abstract class ParallelFlux implements CorePublisher { + + /** + * Take a Publisher and prepare to consume it on multiple 'rails' (one per CPU core) + * in a round-robin fashion. Equivalent to {@link Flux#parallel}. + * + * @param the value type + * @param source the source Publisher + * + * @return the {@link ParallelFlux} instance + */ + public static ParallelFlux from(Publisher source) { + return from(source, Schedulers.DEFAULT_POOL_SIZE, Queues.SMALL_BUFFER_SIZE, + Queues.small()); + } + + /** + * Take a Publisher and prepare to consume it on {@code parallelism} number of 'rails', + * possibly ordered and in a round-robin fashion. + * + * @param the value type + * @param source the source Publisher + * @param parallelism the number of parallel rails + * + * @return the new {@link ParallelFlux} instance + */ + public static ParallelFlux from(Publisher source, + int parallelism) { + return from(source, + parallelism, Queues.SMALL_BUFFER_SIZE, + Queues.small()); + } + + /** + * Take a Publisher and prepare to consume it on {@code parallelism} number of 'rails' + * and in a round-robin fashion and use custom prefetch amount and queue + * for dealing with the source Publisher's values. + * + * @param the value type + * @param source the source Publisher + * @param parallelism the number of parallel rails + * @param prefetch the number of values to prefetch from the source + * @param queueSupplier the queue structure supplier to hold the prefetched values + * from the source until there is a rail ready to process it. + * + * @return the new {@link ParallelFlux} instance + */ + public static ParallelFlux from(Publisher source, + int parallelism, + int prefetch, + Supplier> queueSupplier) { + Objects.requireNonNull(queueSupplier, "queueSupplier"); + Objects.requireNonNull(source, "source"); + + return onAssembly(new ParallelSource<>(source, + parallelism, + prefetch, queueSupplier)); + } + + /** + * Wraps multiple Publishers into a {@link ParallelFlux} which runs them in parallel and + * unordered. + * + * @param the value type + * @param publishers the array of publishers + * + * @return the new {@link ParallelFlux} instance + */ + @SafeVarargs + public static ParallelFlux from(Publisher... publishers) { + return onAssembly(new ParallelArraySource<>(publishers)); + } + + /** + * Perform a fluent transformation to a value via a converter function which receives + * this ParallelFlux. + * + * @param the output value type + * @param converter the converter function from {@link ParallelFlux} to some type + * + * @return the value returned by the converter function + */ + public final U as(Function, U> converter) { + return converter.apply(this); + } + + /** + * Activate traceback (full assembly tracing) for this particular {@link ParallelFlux}, in case of an + * error upstream of the checkpoint. Tracing incurs the cost of an exception stack trace + * creation. + *

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

+ * 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 + * would appear as a component of the composite. In any case, the traceback nature can be detected via + * {@link Exceptions#isTraceback(Throwable)}. + * + * @return the assembly tracing {@link ParallelFlux} + */ + public final ParallelFlux checkpoint() { + AssemblySnapshot stacktrace = new CheckpointHeavySnapshot(null, Traces.callSiteSupplierFactory.get()); + return new ParallelFluxOnAssembly<>(this, stacktrace); + } + + /** + * Activate traceback (assembly marker) for this particular {@link ParallelFlux} by giving it a description that + * will be reflected in the assembly traceback in case of an error upstream of the + * checkpoint. Note that unlike {@link #checkpoint()}, this doesn't create a + * filled stack trace, avoiding the main cost of the operator. + * However, as a trade-off the description must be unique enough for the user to find + * out where this ParallelFlux was assembled. If you only want a generic description, and + * still rely on the stack trace to find the assembly site, use the + * {@link #checkpoint(String, boolean)} variant. + *

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

+ * 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 + * would appear as a component of the composite. In any case, the traceback nature can be detected via + * {@link Exceptions#isTraceback(Throwable)}. + * + * @param description a unique enough description to include in the light assembly traceback. + * @return the assembly marked {@link ParallelFlux} + */ + public final ParallelFlux checkpoint(String description) { + return new ParallelFluxOnAssembly<>(this, new CheckpointLightSnapshot(description)); + } + + /** + * Activate traceback (full assembly tracing or the lighter assembly marking depending on the + * {@code forceStackTrace} option). + *

+ * By setting the {@code forceStackTrace} parameter to {@literal true}, activate assembly + * tracing for this particular {@link ParallelFlux} and give it a description that + * will be reflected in the assembly traceback in case of an error upstream of the + * checkpoint. Note that unlike {@link #checkpoint(String)}, this will incur + * the cost of an exception stack trace creation. The description could for + * example be a meaningful name for the assembled ParallelFlux or a wider correlation ID, + * since the stack trace will always provide enough information to locate where this + * ParallelFlux was assembled. + *

+ * By setting {@code forceStackTrace} to {@literal false}, behaves like + * {@link #checkpoint(String)} and is subject to the same caveat in choosing the + * description. + *

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

+ * 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 + * would appear as a component of the composite. In any case, the traceback nature can be detected via + * {@link Exceptions#isTraceback(Throwable)}. + * + * @param description a description (must be unique enough if forceStackTrace is set + * to false). + * @param forceStackTrace false to make a light checkpoint without a stacktrace, true + * to use a stack trace. + * @return the assembly marked {@link ParallelFlux}. + */ + public final ParallelFlux checkpoint(String description, boolean forceStackTrace) { + final AssemblySnapshot stacktrace; + if (!forceStackTrace) { + stacktrace = new CheckpointLightSnapshot(description); + } + else { + stacktrace = new CheckpointHeavySnapshot(description, Traces.callSiteSupplierFactory.get()); + } + + return new ParallelFluxOnAssembly<>(this, stacktrace); + } + + /** + * Collect the elements in each rail into a collection supplied via a + * collectionSupplier and collected into with a collector action, emitting the + * collection at the end. + * + * @param the collection type + * @param collectionSupplier the supplier of the collection in each rail + * @param collector the collector, taking the per-rail collection and the current + * item + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux collect(Supplier collectionSupplier, + BiConsumer collector) { + return onAssembly(new ParallelCollect<>(this, collectionSupplier, collector)); + } + + /** + * Sorts the 'rails' according to the comparator and returns a full sorted list as a + * Publisher. + *

+ * This operator requires a finite source ParallelFlux. + * + * @param comparator the comparator to compare elements + * + * @return the new Flux instance + */ + public final Mono> collectSortedList(Comparator comparator) { + return collectSortedList(comparator, 16); + } + + /** + * Sorts the 'rails' according to the comparator and returns a full sorted list as a + * Publisher. + *

+ * This operator requires a finite source ParallelFlux. + * + * @param comparator the comparator to compare elements + * @param capacityHint the expected number of total elements + * + * @return the new Mono instance + */ + public final Mono> collectSortedList(Comparator comparator, + int capacityHint) { + int ch = capacityHint / parallelism() + 1; + ParallelFlux> railReduced = + reduce(() -> new ArrayList<>(ch), (a, b) -> { + a.add(b); + return a; + }); + ParallelFlux> railSorted = railReduced.map(list -> { + list.sort(comparator); + return list; + }); + + Mono> merged = railSorted.reduce((a, b) -> sortedMerger(a, b, comparator)); + + return merged; + } + + /** + * Generates and concatenates Publishers on each 'rail', signalling errors immediately + * and generating 2 publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher source and the + * inner Publishers (immediate, boundary, end) + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux concatMap(Function> mapper) { + return concatMap(mapper, 2, ErrorMode.IMMEDIATE); + } + + /** + * Generates and concatenates Publishers on each 'rail', signalling errors immediately + * and using the given prefetch amount for generating Publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param prefetch the number of items to prefetch from each inner Publisher source + * and the inner Publishers (immediate, boundary, end) + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux concatMap(Function> mapper, + int prefetch) { + return concatMap(mapper, prefetch, ErrorMode.IMMEDIATE); + } + + /** + * Generates and concatenates Publishers on each 'rail', delaying errors + * and generating 2 publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * source and the inner Publishers (immediate, boundary, end) + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux concatMapDelayError(Function> mapper) { + return concatMap(mapper, 2, ErrorMode.END); + } + + /** + * Run the specified runnable when a 'rail' completes or signals an error. + * + * @param afterTerminate the callback + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux doAfterTerminate(Runnable afterTerminate) { + Objects.requireNonNull(afterTerminate, "afterTerminate"); + return doOnSignal(this, null, null, null, null, afterTerminate, null, null, null); + } + + /** + * Run the specified runnable when a 'rail' receives a cancellation. + * + * @param onCancel the callback + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux doOnCancel(Runnable onCancel) { + Objects.requireNonNull(onCancel, "onCancel"); + return doOnSignal(this, null, null, null, null, null, null, null, onCancel); + } + + /** + * Run the specified runnable when a 'rail' completes. + * + * @param onComplete the callback + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux doOnComplete(Runnable onComplete) { + Objects.requireNonNull(onComplete, "onComplete"); + return doOnSignal(this, null, null, null, onComplete, null, null, null, null); + } + + /** + * Triggers side-effects when the {@link ParallelFlux} emits an item, fails with an error + * or completes successfully. All these events are represented as a {@link Signal} + * that is passed to the side-effect callback. Note that with {@link ParallelFlux} and + * the {@link #subscribe(Consumer) lambda-based subscribes} or the + * {@link #subscribe(CoreSubscriber[]) array-based one}, onError and onComplete will be + * invoked as many times as there are rails, resulting in as many corresponding + * {@link Signal} being seen in the callback. + *

+ * Use of {@link #subscribe(Subscriber)}, which calls {@link #sequential()}, might + * cancel some rails, resulting in less signals being observed. This is an advanced + * operator, typically used for monitoring of a ParallelFlux. + * + * @param signalConsumer the mandatory callback to call on + * {@link Subscriber#onNext(Object)}, {@link Subscriber#onError(Throwable)} and + * {@link Subscriber#onComplete()} + * @return an observed {@link ParallelFlux} + * @see #doOnNext(Consumer) + * @see #doOnError(Consumer) + * @see #doOnComplete(Runnable) + * @see #subscribe(CoreSubscriber[]) + * @see Signal + */ + public final ParallelFlux doOnEach(Consumer> signalConsumer) { + Objects.requireNonNull(signalConsumer, "signalConsumer"); + return onAssembly(new ParallelDoOnEach<>( + this, + (ctx, v) -> signalConsumer.accept(Signal.next(v, ctx)), + (ctx, e) -> signalConsumer.accept(Signal.error(e, ctx)), + ctx -> signalConsumer.accept(Signal.complete(ctx)) + )); + } + + /** + * Call the specified consumer with the exception passing through any 'rail'. + * + * @param onError the callback + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux doOnError(Consumer onError) { + Objects.requireNonNull(onError, "onError"); + return doOnSignal(this, null, null, onError, null, null, null, null, null); + } + + /** + * Call the specified callback when a 'rail' receives a Subscription from its + * upstream. + *

+ * This method is not intended for capturing the subscription and calling its methods, + * but for side effects like monitoring. For instance, the correct way to cancel a subscription is + * to call {@link Disposable#dispose()} on the Disposable returned by {@link ParallelFlux#subscribe()}. + * + * @param onSubscribe the callback + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux doOnSubscribe(Consumer onSubscribe) { + Objects.requireNonNull(onSubscribe, "onSubscribe"); + return doOnSignal(this, null, null, null, null, null, onSubscribe, null, null); + } + + /** + * Call the specified consumer with the current element passing through any 'rail'. + * + * @param onNext the callback + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux doOnNext(Consumer onNext) { + Objects.requireNonNull(onNext, "onNext"); + return doOnSignal(this, onNext, null, null, null, null, null, null, null); + } + + /** + * Call the specified consumer with the request amount if any rail receives a + * request. + * + * @param onRequest the callback + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux doOnRequest(LongConsumer onRequest) { + Objects.requireNonNull(onRequest, "onRequest"); + return doOnSignal(this, null, null, null, null, null, null, onRequest, null); + } + + /** + * Triggered when the {@link ParallelFlux} terminates, either by completing successfully or with an error. + * @param onTerminate the callback to call on {@link Subscriber#onComplete} or {@link Subscriber#onError} + * + * @return an observed {@link ParallelFlux} + */ + public final ParallelFlux doOnTerminate(Runnable onTerminate) { + Objects.requireNonNull(onTerminate, "onTerminate"); + return doOnSignal(this, + null, + null, + e -> onTerminate.run(), + onTerminate, + null, + null, + null, + null); + } + + /** + * Filters the source values on each 'rail'. + *

+ * Note that the same predicate may be called from multiple threads concurrently. + * + * @param predicate the function returning true to keep a value or false to drop a + * value + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux filter(Predicate predicate) { + Objects.requireNonNull(predicate, "predicate"); + return onAssembly(new ParallelFilter<>(this, predicate)); + } + + /** + * Generates and flattens Publishers on each 'rail'. + *

+ * Errors are not delayed and uses unbounded concurrency along with default inner + * prefetch. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux flatMap(Function> mapper) { + return flatMap(mapper, + false, + Integer.MAX_VALUE, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Generates and flattens Publishers on each 'rail', optionally delaying errors. + *

+ * It uses unbounded concurrency along with default inner prefetch. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param delayError should the errors from the main and the inner sources delayed + * till everybody terminates? + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux flatMap(Function> mapper, + boolean delayError) { + return flatMap(mapper, + delayError, + Integer.MAX_VALUE, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Generates and flattens Publishers on each 'rail', optionally delaying errors and + * having a total number of simultaneous subscriptions to the inner Publishers. + *

+ * It uses a default inner prefetch. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param delayError should the errors from the main and the inner sources delayed + * till everybody terminates? + * @param maxConcurrency the maximum number of simultaneous subscriptions to the + * generated inner Publishers + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux flatMap(Function> mapper, + boolean delayError, + int maxConcurrency) { + return flatMap(mapper, + delayError, + maxConcurrency, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Generates and flattens Publishers on each 'rail', optionally delaying errors, + * having a total number of simultaneous subscriptions to the inner Publishers and + * using the given prefetch amount for the inner Publishers. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param delayError should the errors from the main and the inner sources delayed + * till everybody terminates? + * @param maxConcurrency the maximum number of simultaneous subscriptions to the + * generated inner Publishers + * @param prefetch the number of items to prefetch from each inner Publisher + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux flatMap(Function> mapper, + boolean delayError, + int maxConcurrency, + int prefetch) { + return onAssembly(new ParallelFlatMap<>(this, + mapper, + delayError, + maxConcurrency, + Queues.get(maxConcurrency), + prefetch, Queues.get(prefetch))); + } + + /** + * Exposes the 'rails' as individual GroupedFlux instances, keyed by the rail + * index (zero based). + *

+ * Each group can be consumed only once; requests and cancellation compose through. + * Note that cancelling only one rail may result in undefined behavior. + * + * @return the new Flux instance + */ + public final Flux> groups() { + return Flux.onAssembly(new ParallelGroup<>(this)); + } + + /** + * Hides the identities of this {@link ParallelFlux} and its {@link Subscription} + * as well. + * + * @return a new {@link ParallelFlux} defeating any {@link Publisher} / {@link Subscription} feature-detection + */ + public final ParallelFlux hide() { + return new ParallelFluxHide<>(this); + } + + /** + * Observe all Reactive Streams signals and use {@link Logger} support to handle trace + * implementation. Default will use {@link Level#INFO} and java.util.logging. If SLF4J + * is available, it will be used instead. + *

+ * + *

+ * The default log category will be "reactor.*", a generated operator suffix will + * complete, e.g. "reactor.Parallel.Map". + * + * @return a new unaltered {@link ParallelFlux} + */ + public final ParallelFlux log() { + return log(null, Level.INFO); + } + + /** + * Observe all Reactive Streams signals and use {@link Logger} support to handle trace + * implementation. Default will use {@link Level#INFO} and java.util.logging. If SLF4J + * is available, it will be used instead. + *

+ * + *

+ * + * @param category to be mapped into logger configuration (e.g. org.springframework + * .reactor). If category ends with "." like "reactor.", a generated operator suffix + * will complete, e.g. "reactor.Parallel.Map". + * + * @return a new unaltered {@link ParallelFlux} + */ + public final ParallelFlux log(@Nullable String category) { + return log(category, Level.INFO); + } + + /** + * Observe Reactive Streams signals matching the passed filter {@code options} and use + * {@link Logger} support to handle trace implementation. Default will use the passed + * {@link Level} and java.util.logging. If SLF4J is available, it will be used + * instead. + *

+ * Options allow fine grained filtering of the traced signal, for instance to only + * capture onNext and onError: + *

+	 *     ParallelFlux.log("category", Level.INFO, SignalType.ON_NEXT,
+	 * SignalType.ON_ERROR)
+	 *
+	 * 

+ * + * + * @param category to be mapped into logger configuration (e.g. org.springframework + * .reactor). If category ends with "." like "reactor.", a generated operator + * suffix will complete, e.g. "reactor.Parallel.Map". + * @param level the {@link Level} to enforce for this tracing ParallelFlux (only + * FINEST, FINE, INFO, WARNING and SEVERE are taken into account) + * @param options a vararg {@link SignalType} option to filter log messages + * + * @return a new unaltered {@link ParallelFlux} + */ + public final ParallelFlux log(@Nullable String category, + Level level, + SignalType... options) { + return log(category, level, false, options); + } + + /** + * Observe Reactive Streams signals matching the passed filter {@code options} and use + * {@link Logger} support to handle trace implementation. Default will use the passed + * {@link Level} and java.util.logging. If SLF4J is available, it will be used + * instead. + *

+ * Options allow fine grained filtering of the traced signal, for instance to only + * capture onNext and onError: + *

+	 *     ParallelFlux.log("category", Level.INFO, SignalType.ON_NEXT,
+	 * SignalType.ON_ERROR)
+	 *
+	 * 

+ * + * + * @param category to be mapped into logger configuration (e.g. org.springframework + * .reactor). If category ends with "." like "reactor.", a generated operator + * suffix will complete, e.g. "reactor.ParallelFlux.Map". + * @param level the {@link Level} to enforce for this tracing ParallelFlux (only + * FINEST, FINE, INFO, WARNING and SEVERE are taken into account) + * @param showOperatorLine capture the current stack to display operator + * class/line number. + * @param options a vararg {@link SignalType} option to filter log messages + * + * @return a new unaltered {@link ParallelFlux} + */ + public final ParallelFlux log(@Nullable String category, + Level level, + boolean showOperatorLine, + SignalType... options) { + return onAssembly(new ParallelLog<>(this, new SignalLogger<>(this, category, level, showOperatorLine, options))); + } + + /** + * Maps the source values on each 'rail' to another value. + *

+ * Note that the same mapper function may be called from multiple threads + * concurrently. + * + * @param the output value type + * @param mapper the mapper function turning Ts into Us. + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux map(Function mapper) { + Objects.requireNonNull(mapper, "mapper"); + return onAssembly(new ParallelMap<>(this, mapper)); + } + + /** + * 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()}. + * + * @param name a name for the sequence + * @return the same sequence, but bearing a name + */ + public final ParallelFlux name(String name) { + return ParallelFluxName.createOrAppend(this, name); + } + + /** + * Merges the values from each 'rail', but choose which one to merge by way of a + * provided {@link Comparator}, picking the smallest of all rails. The result is + * exposed back as a {@link Flux}. + *

+ * This version uses a default prefetch of {@link Queues#SMALL_BUFFER_SIZE}. + * + * @param comparator the comparator to choose the smallest value available from all rails + * @return the new Flux instance + * + * @see ParallelFlux#ordered(Comparator, int) + */ + public final Flux ordered(Comparator comparator) { + return ordered(comparator, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Merges the values from each 'rail', but choose which one to merge by way of a + * provided {@link Comparator}, picking the smallest of all rails. The result is + * exposed back as a {@link Flux}. + * + * @param comparator the comparator to choose the smallest value available from all rails + * @param prefetch the prefetch to use + * @return the new Flux instance + * + * @see ParallelFlux#ordered(Comparator) + */ + public final Flux ordered(Comparator comparator, int prefetch) { + return new ParallelMergeOrdered<>(this, prefetch, comparator); + } + + /** + * Returns the number of expected parallel Subscribers. + * + * @return the number of expected parallel Subscribers + */ + public abstract int parallelism(); + + /** + * Reduces all values within a 'rail' and across 'rails' with a reducer function into + * a single sequential value. + *

+ * Note that the same reducer function may be called from multiple threads + * concurrently. + * + * @param reducer the function to reduce two values into one. + * + * @return the new Mono instance emitting the reduced value or empty if the + * {@link ParallelFlux} was empty + */ + public final Mono reduce(BiFunction reducer) { + Objects.requireNonNull(reducer, "reducer"); + return Mono.onAssembly(new ParallelMergeReduce<>(this, reducer)); + } + + /** + * Reduces all values within a 'rail' to a single value (with a possibly different + * type) via a reducer function that is initialized on each rail from an + * initialSupplier value. + *

+ * Note that the same mapper function may be called from multiple threads + * concurrently. + * + * @param the reduced output type + * @param initialSupplier the supplier for the initial value + * @param reducer the function to reduce a previous output of reduce (or the initial + * value supplied) with a current source value. + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux reduce(Supplier initialSupplier, + BiFunction reducer) { + Objects.requireNonNull(initialSupplier, "initialSupplier"); + Objects.requireNonNull(reducer, "reducer"); + return onAssembly(new ParallelReduceSeed<>(this, initialSupplier, reducer)); + } + + /** + * Specifies where each 'rail' will observe its incoming values with possible + * work-stealing and default prefetch amount. + *

+ * This operator uses the default prefetch size returned by {@code + * Queues.SMALL_BUFFER_SIZE}. + *

+ * The operator will call {@code Scheduler.createWorker()} as many times as this + * ParallelFlux's parallelism level is. + *

+ * No assumptions are made about the Scheduler's parallelism level, if the Scheduler's + * parallelism level is lower than the ParallelFlux's, some rails may end up on + * the same thread/worker. + *

+ * This operator doesn't require the Scheduler to be trampolining as it does its own + * built-in trampolining logic. + * + * @param scheduler the scheduler to use + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux runOn(Scheduler scheduler) { + return runOn(scheduler, Queues.SMALL_BUFFER_SIZE); + } + + /** + * Specifies where each 'rail' will observe its incoming values with possible + * work-stealing and a given prefetch amount. + *

+ * This operator uses the default prefetch size returned by {@code + * Queues.SMALL_BUFFER_SIZE}. + *

+ * The operator will call {@code Scheduler.createWorker()} as many times as this + * ParallelFlux's parallelism level is. + *

+ * No assumptions are made about the Scheduler's parallelism level, if the Scheduler's + * parallelism level is lower than the ParallelFlux's, some rails may end up on + * the same thread/worker. + *

+ * This operator doesn't require the Scheduler to be trampolining as it does its own + * built-in trampolining logic. + * + * @param scheduler the scheduler to use that rail's worker has run out of work. + * @param prefetch the number of values to request on each 'rail' from the source + * + * @return the new {@link ParallelFlux} instance + */ + public final ParallelFlux runOn(Scheduler scheduler, int prefetch) { + Objects.requireNonNull(scheduler, "scheduler"); + return onAssembly(new ParallelRunOn<>(this, + scheduler, + prefetch, + Queues.get(prefetch))); + } + + /** + * Merges the values from each 'rail' in a round-robin or same-order fashion and + * exposes it as a regular Publisher sequence, running with a default prefetch value + * for the rails. + *

+ * This operator uses the default prefetch size returned by {@code + * Queues.SMALL_BUFFER_SIZE}. + * + * @return the new Flux instance + * + * @see ParallelFlux#sequential(int) + */ + public final Flux sequential() { + return sequential(Queues.SMALL_BUFFER_SIZE); + } + + /** + * Merges the values from each 'rail' in a round-robin or same-order fashion and + * exposes it as a regular Publisher sequence, running with a give prefetch value for + * the rails. + * + * @param prefetch the prefetch amount to use for each rail + * + * @return the new Flux instance + */ + public final Flux sequential(int prefetch) { + return Flux.onAssembly(new ParallelMergeSequential<>(this, + prefetch, + Queues.get(prefetch))); + } + + /** + * Sorts the 'rails' of this {@link ParallelFlux} and returns a Publisher that + * sequentially picks the smallest next value from the rails. + *

+ * This operator requires a finite source ParallelFlux. + * + * @param comparator the comparator to use + * + * @return the new Flux instance + */ + public final Flux sorted(Comparator comparator) { + return sorted(comparator, 16); + } + + /** + * Sorts the 'rails' of this {@link ParallelFlux} and returns a Publisher that + * sequentially picks the smallest next value from the rails. + *

+ * This operator requires a finite source ParallelFlux. + * + * @param comparator the comparator to use + * @param capacityHint the expected number of total elements + * + * @return the new Flux instance + */ + public final Flux sorted(Comparator comparator, int capacityHint) { + int ch = capacityHint / parallelism() + 1; + ParallelFlux> railReduced = reduce(() -> new ArrayList<>(ch), (a, b) -> { + a.add(b); + return a; + }); + ParallelFlux> railSorted = railReduced.map(list -> { + list.sort(comparator); + return list; + }); + + return Flux.onAssembly(new ParallelMergeSort<>(railSorted, comparator)); + } + + /** + * Subscribes an array of Subscribers to this {@link ParallelFlux} and triggers the + * execution chain for all 'rails'. + * + * @param subscribers the subscribers array to run in parallel, the number of items + * must be equal to the parallelism level of this ParallelFlux + */ + public abstract void subscribe(CoreSubscriber[] subscribers); + + /** + * Subscribes to this {@link ParallelFlux} and triggers the execution chain for all + * 'rails'. + */ + public final Disposable subscribe(){ + return subscribe(null, null, null); + } + + /** + * Subscribes to this {@link ParallelFlux} by providing an onNext callback and + * triggers the execution chain for all 'rails'. + * + * @param onNext consumer of onNext signals + */ + public final Disposable subscribe(Consumer onNext){ + return subscribe(onNext, null, null); + } + + /** + * Subscribes to this {@link ParallelFlux} by providing an onNext and onError callback + * and triggers the execution chain for all 'rails'. + * + * @param onNext consumer of onNext signals + * @param onError consumer of error signal + */ + public final Disposable subscribe(@Nullable Consumer onNext, Consumer + onError){ + return subscribe(onNext, onError, null); + } + + /** + * Subscribes to this {@link ParallelFlux} by providing an onNext, onError and + * onComplete callback and triggers the execution chain for all 'rails'. + * + * @param onNext consumer of onNext signals + * @param onError consumer of error signal + * @param onComplete callback on completion signal + */ + public final Disposable subscribe( + @Nullable Consumer onNext, + @Nullable Consumer onError, + @Nullable Runnable onComplete) { + return this.subscribe(onNext, onError, onComplete, null, (Context) null); + } + + @Override + @SuppressWarnings("unchecked") + public final void subscribe(CoreSubscriber s) { + FluxHide.SuppressFuseableSubscriber subscriber = + new FluxHide.SuppressFuseableSubscriber<>(Operators.toCoreSubscriber(s)); + + sequential().subscribe(Operators.toCoreSubscriber(subscriber)); + } + + /** + * Subscribes to this {@link ParallelFlux} by providing an onNext, onError, + * onComplete and onSubscribe callback and triggers the execution chain for all + * 'rails'. + * + * @param onNext consumer of onNext signals + * @param onError consumer of error signal + * @param onComplete callback on completion signal + * @param onSubscribe consumer of the subscription signal + */ //TODO maybe deprecate in 3.4, provided there is at least an alternative for tests + public final Disposable subscribe( + @Nullable Consumer onNext, + @Nullable Consumer onError, + @Nullable Runnable onComplete, + @Nullable Consumer onSubscribe) { + return this.subscribe(onNext, onError, onComplete, onSubscribe, null); + } + + /** + * Subscribes to this {@link ParallelFlux} by providing an onNext, onError and + * onComplete callback as well as an initial {@link Context}, then trigger the execution chain for all + * 'rails'. + * + * @param onNext consumer of onNext signals + * @param onError consumer of error signal + * @param onComplete callback on completion signal + * @param initialContext {@link Context} for the rails + */ + public final Disposable subscribe( + @Nullable Consumer onNext, + @Nullable Consumer onError, + @Nullable Runnable onComplete, + @Nullable Context initialContext) { + return this.subscribe(onNext, onError, onComplete, null, initialContext); + } + + final Disposable subscribe( + @Nullable Consumer onNext, + @Nullable Consumer onError, + @Nullable Runnable onComplete, + @Nullable Consumer onSubscribe, + @Nullable Context initialContext) { + CorePublisher publisher = Operators.onLastAssembly(this); + if (publisher instanceof ParallelFlux) { + @SuppressWarnings("unchecked") + LambdaSubscriber[] subscribers = new LambdaSubscriber[parallelism()]; + + int i = 0; + while(i < subscribers.length){ + subscribers[i++] = + new LambdaSubscriber<>(onNext, onError, onComplete, onSubscribe, initialContext); + } + + ((ParallelFlux) publisher).subscribe(subscribers); + + return Disposables.composite(subscribers); + } + else { + LambdaSubscriber subscriber = + new LambdaSubscriber<>(onNext, onError, onComplete, onSubscribe, initialContext); + + publisher.subscribe(Operators.toCoreSubscriber(new FluxHide.SuppressFuseableSubscriber<>(subscriber))); + + return subscriber; + } + } + + /** + * Merge the rails into a {@link #sequential()} Flux and + * {@link Flux#subscribe(Subscriber) subscribe} to said Flux. + * + * @param s the subscriber to use on {@link #sequential()} Flux + */ + @Override + @SuppressWarnings("unchecked") + public final void subscribe(Subscriber s) { + FluxHide.SuppressFuseableSubscriber subscriber = + new FluxHide.SuppressFuseableSubscriber<>(Operators.toCoreSubscriber(s)); + + Operators.onLastAssembly(sequential()).subscribe(Operators.toCoreSubscriber(subscriber)); + } + + /** + * Tag this ParallelFlux 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()}). + * + * @param key a tag key + * @param value a tag value + * @return the same sequence, but bearing tags + */ + public final ParallelFlux tag(String key, String value) { + return ParallelFluxName.createOrAppend(this, key, value); + } + + + + /** + * Emit an onComplete or onError signal once all values across 'rails' have been observed. + * + * @return the new Mono instance emitting the reduced value or empty if the + * {@link ParallelFlux} was empty + */ + public final Mono then() { + return Mono.onAssembly(new ParallelThen(this)); + } + + /** + * Allows composing operators, in assembly time, on top of this {@link ParallelFlux} + * and returns another {@link ParallelFlux} with composed features. + * + * @param the output value type + * @param composer the composer function from {@link ParallelFlux} (this) to another + * ParallelFlux + * + * @return the {@link ParallelFlux} returned by the function + */ + public final ParallelFlux transform(Function, ParallelFlux> composer) { + return onAssembly(as(composer)); + } + + /** + * Allows composing operators off the groups (or 'rails'), as individual {@link GroupedFlux} + * instances keyed by the zero based rail's index. The transformed groups are + * {@link Flux#parallel parallelized} back once the transformation has been applied. + * Since groups are generated anew per each subscription, this is all done in a "lazy" + * fashion where each subscription trigger distinct applications of the {@link Function}. + *

+ * Note that like in {@link #groups()}, requests and cancellation compose through, and + * cancelling only one rail may result in undefined behavior. + * + * @param composer the composition function to apply on each {@link GroupedFlux rail} + * @param the type of the resulting parallelized flux + * @return a {@link ParallelFlux} of the composed groups + */ + public final ParallelFlux transformGroups(Function, + ? extends Publisher> composer) { + if (getPrefetch() > -1) { + return from(groups().flatMap(composer::apply), + parallelism(), getPrefetch(), + Queues.small()); + } + else { + return from(groups().flatMap(composer::apply), parallelism()); + } + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + /** + * Validates the number of subscribers and returns true if their number matches the + * parallelism level of this ParallelFlux. + * + * @param subscribers the array of Subscribers + * + * @return true if the number of subscribers equals to the parallelism level + */ + protected final boolean validate(Subscriber[] subscribers) { + int p = parallelism(); + if (subscribers.length != p) { + IllegalArgumentException iae = new IllegalArgumentException("parallelism = " + + "" + p + ", subscribers = " + subscribers.length); + for (Subscriber s : subscribers) { + Operators.error(s, iae); + } + return false; + } + return true; + } + + /** + * Generates and concatenates Publishers on each 'rail', optionally delaying errors + * and using the given prefetch amount for generating Publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param prefetch the number of items to prefetch from each inner Publisher + * @param errorMode the error handling, i.e., when to report errors from the main + * source and the inner Publishers (immediate, boundary, end) + * + * @return the new {@link ParallelFlux} instance + */ + final ParallelFlux concatMap(Function> mapper, + int prefetch, + ErrorMode errorMode) { + return onAssembly(new ParallelConcatMap<>(this, + mapper, + Queues.get(prefetch), + prefetch, + errorMode)); + } + + /** + * Generates and concatenates Publishers on each 'rail', delaying errors + * and using the given prefetch amount for generating Publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param delayUntilEnd true if delayed until all sources are concatenated + * @param prefetch the number of items to prefetch from each inner Publisher + * source and the inner Publishers (immediate, boundary, end) + * + * @return the new {@link ParallelFlux} instance + */ + final ParallelFlux concatMapDelayError(Function> mapper, + boolean delayUntilEnd, + int prefetch) { + return concatMap(mapper, prefetch, delayUntilEnd ? ErrorMode.END: ErrorMode.BOUNDARY); + } + + /** + * Generates and concatenates Publishers on each 'rail', delaying errors + * and using the given prefetch amount for generating Publishers upfront. + * + * @param the result type + * @param mapper the function to map each rail's value into a Publisher + * @param prefetch the number of items to prefetch from each inner Publisher + * source and the inner Publishers (immediate, boundary, end) + * + * @return the new {@link ParallelFlux} instance + */ + final ParallelFlux concatMapDelayError(Function> mapper, int prefetch) { + return concatMap(mapper, prefetch, ErrorMode.END); + } + + /** + * The prefetch configuration of the component + * + * @return the prefetch configuration of the component + */ + public int getPrefetch() { + return -1; + } + + + /** + * Invoke {@link Hooks} pointcut given a {@link ParallelFlux} and returning an + * eventually new {@link ParallelFlux} + * + * @param the value type + * @param source the source to wrap + * + * @return the potentially wrapped source + */ + @SuppressWarnings("unchecked") + protected static ParallelFlux onAssembly(ParallelFlux source) { + Function hook = Hooks.onEachOperatorHook; + if(hook != null) { + source = (ParallelFlux) hook.apply(source); + } + if (Hooks.GLOBAL_TRACE) { + AssemblySnapshot stacktrace = new AssemblySnapshot(null, Traces.callSiteSupplierFactory.get()); + source = (ParallelFlux) Hooks.addAssemblyInfo(source, stacktrace); + } + return source; + } + + @SuppressWarnings("unchecked") + static ParallelFlux doOnSignal(ParallelFlux source, + @Nullable Consumer onNext, + @Nullable Consumer onAfterNext, + @Nullable Consumer onError, + @Nullable Runnable onComplete, + @Nullable Runnable onAfterTerminate, + @Nullable Consumer onSubscribe, + @Nullable LongConsumer onRequest, + @Nullable Runnable onCancel) { + return onAssembly(new ParallelPeek<>(source, + onNext, + onAfterNext, + onError, + onComplete, + onAfterTerminate, + onSubscribe, + onRequest, + onCancel)); + } + + static final List sortedMerger(List a, List b, Comparator comparator) { + int n = a.size() + b.size(); + if (n == 0) { + return new ArrayList<>(); + } + List both = new ArrayList<>(n); + + Iterator at = a.iterator(); + Iterator bt = b.iterator(); + + T s1 = at.hasNext() ? at.next() : null; + T s2 = bt.hasNext() ? bt.next() : null; + + while (s1 != null && s2 != null) { + if (comparator.compare(s1, s2) < 0) { // s1 comes before s2 + both.add(s1); + s1 = at.hasNext() ? at.next() : null; + } + else { + both.add(s2); + s2 = bt.hasNext() ? bt.next() : null; + } + } + + if (s1 != null) { + both.add(s1); + while (at.hasNext()) { + both.add(at.next()); + } + } + else if (s2 != null) { + both.add(s2); + while (bt.hasNext()) { + both.add(bt.next()); + } + } + + return both; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxHide.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxHide.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxHide.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016-2021 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.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Hides the identities of the upstream Publisher object and its Subscription as well. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class ParallelFluxHide extends ParallelFlux implements Scannable{ + + final ParallelFlux source; + + ParallelFluxHide(ParallelFlux source) { + this.source = source; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + @Nullable + 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; + + return null; + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") CoreSubscriber[] parents = + new CoreSubscriber[n]; + for (int i = 0; i < n; i++) { + parents[i] = new FluxHide.HideSubscriber<>(subscribers[i]); + } + + source.subscribe(parents); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxName.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxName.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxName.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2016-2021 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.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +import static reactor.core.Scannable.Attr.RUN_STYLE; +import static reactor.core.Scannable.Attr.RunStyle.SYNC; + +/** + * Hides the identities of the upstream Publisher object and its Subscription as well. + * + * @param the value type + * @see Reactive-Streams-Commons + */ +final class ParallelFluxName extends ParallelFlux implements Scannable{ + + final ParallelFlux source; + + final String name; + + final Set> tags; + + @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<>(source, name, null); + } + + @SuppressWarnings("unchecked") + static ParallelFlux createOrAppend(ParallelFlux source, String tagName, String tagValue) { + Objects.requireNonNull(tagName, "tagName"); + Objects.requireNonNull(tagValue, "tagValue"); + + Set> tags = Collections.singleton(Tuples.of(tagName, tagValue)); + + if (source instanceof ParallelFluxName) { + ParallelFluxName s = (ParallelFluxName) source; + if(s.tags != null) { + tags = new HashSet<>(tags); + tags.addAll(s.tags); + } + return new ParallelFluxName<>(s.source, s.name, tags); + } + return new ParallelFluxName<>(source, null, tags); + } + + ParallelFluxName(ParallelFlux source, + @Nullable String name, + @Nullable Set> tags) { + this.source = source; + this.name = name; + this.tags = tags; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.NAME) { + return name; + } + + if (key == Attr.TAGS && tags != null) { + return tags.stream(); + } + + if (key == Attr.PARENT) return source; + if (key == Attr.PREFETCH) return getPrefetch(); + + if (key == RUN_STYLE) { + return SYNC; + } + + return null; + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + source.subscribe(subscribers); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxOnAssembly.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxOnAssembly.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelFluxOnAssembly.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2016-2021 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.Scannable; +import reactor.core.publisher.FluxOnAssembly.AssemblySnapshot; +import reactor.util.annotation.Nullable; + +/** + * Captures the current stacktrace when this connectable publisher is created and makes it + * available/visible for debugging purposes from the inner Subscriber. + *

+ * Note that getting a stacktrace is a costly operation. + *

+ * The operator sanitizes the stacktrace and removes noisy entries such as:

    + *
  • java.lang.Thread entries
  • method references with source line of 1 (bridge + * methods)
  • Tomcat worker thread entries
  • JUnit setup
+ * + * @param the value type passing through + * @see https://github.com/reactor/reactive-streams-commons + */ +final class ParallelFluxOnAssembly extends ParallelFlux + implements Fuseable, AssemblyOp, Scannable { + + final ParallelFlux source; + final AssemblySnapshot stacktrace; + + /** + * Create an assembly trace wrapping a {@link ParallelFlux}. + */ + ParallelFluxOnAssembly(ParallelFlux source, AssemblySnapshot stacktrace) { + this.source = source; + this.stacktrace = stacktrace; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") CoreSubscriber[] parents = + new CoreSubscriber[n]; + CoreSubscriber s; + for (int i = 0; i < n; i++) { + s = subscribers[i]; + if (s instanceof ConditionalSubscriber) { + ConditionalSubscriber cs = (ConditionalSubscriber) s; + s = new FluxOnAssembly.OnAssemblyConditionalSubscriber<>(cs, + stacktrace, + source, + this); + } + else { + s = new FluxOnAssembly.OnAssemblySubscriber<>(s, stacktrace, source, this); + } + parents[i] = s; + } + + source.subscribe(parents); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.ACTUAL_METADATA) return !stacktrace.isCheckpoint; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public String stepName() { + return stacktrace.operatorAssemblyInformation(); + } + + @Override + public String toString() { + return stacktrace.operatorAssemblyInformation(); + } +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelGroup.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelGroup.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelGroup.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Exposes the 'rails' as individual GroupedFlux instances, keyed by the rail index (zero based). + *

+ * Each group can be consumed only once; requests and cancellation compose through. Note + * that cancelling only one rail may result in undefined behavior. + * + * @param the value type + */ +final class ParallelGroup extends Flux> implements + Scannable, Fuseable { + + final ParallelFlux source; + + ParallelGroup(ParallelFlux source) { + this.source = source; + } + + @Override + public void subscribe(CoreSubscriber> actual) { + int n = source.parallelism(); + + @SuppressWarnings("unchecked") + ParallelInnerGroup[] groups = new ParallelInnerGroup[n]; + + for (int i = 0; i < n; i++) { + groups[i] = new ParallelInnerGroup<>(i); + } + + FluxArray.subscribe(actual, groups); + + source.subscribe(groups); + } + + @Override + @Nullable + 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; + + return null; + } + + static final class ParallelInnerGroup extends GroupedFlux + implements InnerOperator { + final int key; + + volatile int once; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(ParallelInnerGroup.class, "once"); + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(ParallelInnerGroup.class, Subscription.class, "s"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(ParallelInnerGroup.class, "requested"); + + CoreSubscriber actual; + + ParallelInnerGroup(int key) { + this.key = key; + } + + @Override + public Integer key() { + return key; + } + + @Override + public void subscribe(CoreSubscriber actual) { + if (ONCE.compareAndSet(this, 0, 1)) { + this.actual = actual; + actual.onSubscribe(this); + } else { + Operators.error(actual, new IllegalStateException("This ParallelGroup can be subscribed to at most once.")); + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerOperator.super.scanUnsafe(key); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + long r = REQUESTED.getAndSet(this, 0L); + if (r != 0L) { + s.request(r); + } + } + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable t) { + actual.onError(t); + } + + @Override + public void onComplete() { + actual.onComplete(); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Subscription a = s; + if (a == null) { + Operators.addCap(REQUESTED, this, n); + + a = s; + if (a != null) { + long r = REQUESTED.getAndSet(this, 0L); + if (r != 0L) { + a.request(n); + } + } + } else { + a.request(n); + } + } + } + + @Override + public void cancel() { + Operators.terminate(S, this); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelLift.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelLift.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelLift.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * @author Stephane Maldini + */ +final class ParallelLift extends ParallelFlux implements Scannable { + + final Operators.LiftFunction liftFunction; + + final ParallelFlux source; + + ParallelLift(ParallelFlux p, + Operators.LiftFunction liftFunction) { + this.source = Objects.requireNonNull(p, "source"); + this.liftFunction = liftFunction; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return source; + } + if (key == Attr.PREFETCH) { + return getPrefetch(); + } + if (key == Attr.RUN_STYLE) { + return Scannable.from(source).scanUnsafe(key); + } + if (key == Attr.LIFTER) { + return liftFunction.name; + } + + return null; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return Scannable.super.stepName(); + } + + @Override + public void subscribe(CoreSubscriber[] s) { + @SuppressWarnings("unchecked") CoreSubscriber[] subscribers = + new CoreSubscriber[parallelism()]; + + int i = 0; + while (i < subscribers.length) { + subscribers[i] = + Objects.requireNonNull(liftFunction.lifter.apply(source, s[i]), + "Lifted subscriber MUST NOT be null"); + i++; + } + + source.subscribe(subscribers); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelLiftFuseable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelLiftFuseable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelLiftFuseable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * @author Stephane Maldini + * @author Simon Baslé + */ +final class ParallelLiftFuseable extends ParallelFlux + implements Scannable, Fuseable { + + final Operators.LiftFunction liftFunction; + + final ParallelFlux source; + + ParallelLiftFuseable(ParallelFlux p, + Operators.LiftFunction liftFunction) { + this.source = Objects.requireNonNull(p, "source"); + this.liftFunction = liftFunction; + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return source; + } + if (key == Attr.PREFETCH) { + return getPrefetch(); + } + if (key == Attr.RUN_STYLE) { + return Scannable.from(source).scanUnsafe(key); + } + if (key == Attr.LIFTER) { + return liftFunction.name; + } + + return null; + } + + @Override + public String stepName() { + if (source instanceof Scannable) { + return Scannable.from(source).stepName(); + } + return Scannable.super.stepName(); + } + + @Override + public void subscribe(CoreSubscriber[] s) { + @SuppressWarnings("unchecked") CoreSubscriber[] subscribers = + new CoreSubscriber[parallelism()]; + + int i = 0; + while (i < subscribers.length) { + CoreSubscriber actual = s[i]; + CoreSubscriber converted = + Objects.requireNonNull(liftFunction.lifter.apply(source, actual), + "Lifted subscriber MUST NOT be null"); + + Objects.requireNonNull(converted, "Lifted subscriber MUST NOT be null"); + + if (actual instanceof Fuseable.QueueSubscription + && !(converted instanceof QueueSubscription)) { + //user didn't produce a QueueSubscription, original was one + converted = new FluxHide.SuppressFuseableSubscriber<>(converted); + } + //otherwise QS is not required or user already made a compatible conversion + subscribers[i] = converted; + i++; + } + + source.subscribe(subscribers); + } + + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelLog.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelLog.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelLog.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2016-2021 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.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Execute a Consumer in each 'rail' for the current element passing through. + * + * @param the value type + */ +final class ParallelLog extends ParallelFlux implements Scannable { + + final ParallelFlux source; + + final SignalPeek log; + + ParallelLog(ParallelFlux source, + SignalPeek log + ) { + this.source = source; + this.log = log; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + CoreSubscriber[] parents = new CoreSubscriber[n]; + + boolean conditional = subscribers[0] instanceof Fuseable.ConditionalSubscriber; + + for (int i = 0; i < n; i++) { + if (conditional) { + parents[i] = new FluxPeekFuseable.PeekConditionalSubscriber<>( + (Fuseable.ConditionalSubscriber)subscribers[i], log); + } + else { + parents[i] = new FluxPeek.PeekSubscriber<>(subscribers[i], log); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + @Nullable + 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; + + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelMap.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelMap.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelMap.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016-2021 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.Function; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Maps each 'rail' of the source ParallelFlux with a mapper function. + * + * @param the input value type + * @param the output value type + */ +final class ParallelMap extends ParallelFlux implements Scannable { + + final ParallelFlux source; + + final Function mapper; + + ParallelMap(ParallelFlux source, Function mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + @Nullable + 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; + + return null; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + CoreSubscriber[] parents = new CoreSubscriber[n]; + + boolean conditional = subscribers[0] instanceof Fuseable.ConditionalSubscriber; + + for (int i = 0; i < n; i++) { + if (conditional) { + parents[i] = + new FluxMap.MapConditionalSubscriber<>((Fuseable.ConditionalSubscriber) subscribers[i], + mapper); + } + else { + parents[i] = new FluxMap.MapSubscriber<>(subscribers[i], mapper); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeOrdered.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeOrdered.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeOrdered.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2016-2021 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.Comparator; + +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Merges the individual 'rails' of the source {@link ParallelFlux} assuming a total order + * of the values, by picking the smallest available value from each rail, resulting in + * a single regular Publisher sequence (exposed as a {@link Flux}). + * + * @param the value type + * @author Simon Baslé + */ +final class ParallelMergeOrdered extends Flux implements Scannable { + + final ParallelFlux source; + final int prefetch; + final Comparator valueComparator; + + ParallelMergeOrdered(ParallelFlux source, int prefetch, + Comparator valueComparator) { + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.source = source; + this.prefetch = prefetch; + this.valueComparator = valueComparator; + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void subscribe(CoreSubscriber actual) { + FluxMergeComparing.MergeOrderedMainProducer + main = new FluxMergeComparing.MergeOrderedMainProducer<>(actual, valueComparator, prefetch, source.parallelism(), true); + actual.onSubscribe(main); + source.subscribe(main.subscribers); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeReduce.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeReduce.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeReduce.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2016-2021 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.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiFunction; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Reduces all 'rails' into a single value which then gets reduced into a single + * Publisher sequence. + * + * @param the value type + */ +final class ParallelMergeReduce extends Mono implements Scannable, Fuseable { + + final ParallelFlux source; + + final BiFunction reducer; + + ParallelMergeReduce(ParallelFlux source, + BiFunction reducer) { + this.source = source; + this.reducer = reducer; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void subscribe(CoreSubscriber actual) { + MergeReduceMain parent = + new MergeReduceMain<>(actual, source.parallelism(), reducer); + actual.onSubscribe(parent); + + source.subscribe(parent.subscribers); + } + + static final class MergeReduceMain + extends Operators.MonoSubscriber { + + final MergeReduceInner[] subscribers; + + final BiFunction reducer; + + volatile SlotPair current; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + CURRENT = AtomicReferenceFieldUpdater.newUpdater( + MergeReduceMain.class, + SlotPair.class, + "current"); + + volatile int remaining; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater + REMAINING = AtomicIntegerFieldUpdater.newUpdater( + MergeReduceMain.class, + "remaining"); + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + ERROR = AtomicReferenceFieldUpdater.newUpdater( + MergeReduceMain.class, + Throwable.class, + "error"); + + MergeReduceMain(CoreSubscriber subscriber, + int n, + BiFunction reducer) { + super(subscriber); + @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); + } + + @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.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Nullable + SlotPair addValue(T value) { + for (; ; ) { + SlotPair curr = current; + + if (curr == null) { + curr = new SlotPair<>(); + if (!CURRENT.compareAndSet(this, null, curr)) { + continue; + } + } + + int c = curr.tryAcquireSlot(); + if (c < 0) { + CURRENT.compareAndSet(this, curr, null); + continue; + } + if (c == 0) { + curr.first = value; + } + else { + curr.second = value; + } + + if (curr.releaseSlot()) { + CURRENT.compareAndSet(this, curr, null); + return curr; + } + return null; + } + } + + @Override + public void cancel() { + for (MergeReduceInner inner : subscribers) { + inner.cancel(); + } + super.cancel(); + } + + void innerError(Throwable ex) { + if(ERROR.compareAndSet(this, null, ex)){ + cancel(); + actual.onError(ex); + } + else if(error != ex) { + Operators.onErrorDropped(ex, actual.currentContext()); + } + } + + void innerComplete(@Nullable T value) { + if (value != null) { + for (; ; ) { + SlotPair sp = addValue(value); + + if (sp != null) { + + try { + value = Objects.requireNonNull(reducer.apply(sp.first, + sp.second), "The reducer returned a null value"); + } + catch (Throwable ex) { + innerError(Operators.onOperatorError(this, ex, + actual.currentContext())); + return; + } + } + else { + break; + } + } + } + + if (REMAINING.decrementAndGet(this) == 0) { + SlotPair sp = current; + CURRENT.lazySet(this, null); + + if (sp != null) { + complete(sp.first); + } + else { + actual.onComplete(); + } + } + } + } + + static final class MergeReduceInner implements InnerConsumer { + + final MergeReduceMain parent; + + final BiFunction reducer; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + S = AtomicReferenceFieldUpdater.newUpdater( + MergeReduceInner.class, + Subscription.class, + "s"); + + T value; + + boolean done; + + MergeReduceInner(MergeReduceMain parent, + BiFunction reducer) { + this.parent = parent; + this.reducer = reducer; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return s; + if (key == Attr.TERMINATED) return done; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.BUFFERED) return value != null ? 1 : 0; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, currentContext()); + return; + } + T v = value; + + if (v == null) { + value = t; + } + else { + + try { + v = Objects.requireNonNull(reducer.apply(v, t), "The reducer returned a null value"); + } + catch (Throwable ex) { + onError(Operators.onOperatorError(s, ex, t, currentContext())); + return; + } + + value = v; + } + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, parent.currentContext()); + return; + } + done = true; + parent.innerError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + parent.innerComplete(value); + } + + void cancel() { + Operators.terminate(S, this); + } + } + + static final class SlotPair { + + T first; + + T second; + + volatile int acquireIndex; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater ACQ = + AtomicIntegerFieldUpdater.newUpdater(SlotPair.class, "acquireIndex"); + + volatile int releaseIndex; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater REL = + AtomicIntegerFieldUpdater.newUpdater(SlotPair.class, "releaseIndex"); + + int tryAcquireSlot() { + for (; ; ) { + int acquired = acquireIndex; + if (acquired >= 2) { + return -1; + } + + if (ACQ.compareAndSet(this, acquired, acquired + 1)) { + return acquired; + } + } + } + + boolean releaseSlot() { + return REL.incrementAndGet(this) == 2; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeSequential.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeSequential.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeSequential.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2016-2021 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.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Supplier; +import java.util.stream.Stream; + +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; + +/** + * Merges the individual 'rails' of the source ParallelFlux, unordered, + * into a single regular Publisher sequence (exposed as reactor.core.publisher.Flux). + * + * @param the value type + */ +final class ParallelMergeSequential extends Flux implements Scannable { + final ParallelFlux source; + final int prefetch; + final Supplier> queueSupplier; + + ParallelMergeSequential(ParallelFlux source, int prefetch, Supplier> queueSupplier) { + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.source = source; + this.prefetch = prefetch; + this.queueSupplier = queueSupplier; + } + + @Override + @Nullable + 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; + + return null; + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public void subscribe(CoreSubscriber actual) { + MergeSequentialMain parent = new MergeSequentialMain<>(actual, source + .parallelism(), prefetch, queueSupplier); + actual.onSubscribe(parent); + source.subscribe(parent.subscribers); + } + + static final class MergeSequentialMain implements InnerProducer { + + final MergeSequentialInner[] subscribers; + + final Supplier> queueSupplier; + final CoreSubscriber actual; + + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(MergeSequentialMain.class, Throwable.class, "error"); + + volatile int wip; + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(MergeSequentialMain.class, "wip"); + volatile long requested; + + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(MergeSequentialMain.class, "requested"); + volatile boolean cancelled; + + volatile int done; + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater DONE = + AtomicIntegerFieldUpdater.newUpdater(MergeSequentialMain.class, "done"); + volatile Throwable error; + + MergeSequentialMain(CoreSubscriber actual, int n, int + prefetch, + Supplier> queueSupplier) { + this.actual = actual; + this.queueSupplier = queueSupplier; + @SuppressWarnings("unchecked") + MergeSequentialInner[] a = new MergeSequentialInner[n]; + + for (int i = 0; i < n; i++) { + a[i] = new MergeSequentialInner<>(this, prefetch); + } + + this.subscribers = a; + DONE.lazySet(this, n); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.TERMINATED) return done == 0 ; + if (key == Attr.ERROR) return error; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + drain(); + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + + cancelAll(); + + if (WIP.getAndIncrement(this) == 0) { + cleanup(); + } + } + } + + void cancelAll() { + for (MergeSequentialInner s : subscribers) { + s.cancel(); + } + } + + void cleanup() { + for (MergeSequentialInner s : subscribers) { + s.queue = null; + } + } + + void onNext(MergeSequentialInner inner, T value) { + if (wip == 0 && WIP.compareAndSet(this, 0, 1)) { + if (requested != 0) { + actual.onNext(value); + if (requested != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + inner.requestOne(); + } else { + Queue q = inner.getQueue(queueSupplier); + + if(!q.offer(value)){ + onError(Operators.onOperatorError(this, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), value, + actual.currentContext())); + return; + } + } + if (WIP.decrementAndGet(this) == 0) { + return; + } + } else { + Queue q = inner.getQueue(queueSupplier); + + if(!q.offer(value)){ + onError(Operators.onOperatorError(this, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), value, + actual.currentContext())); + return; + } + + if (WIP.getAndIncrement(this) != 0) { + return; + } + } + + drainLoop(); + } + + void onError(Throwable ex) { + if(ERROR.compareAndSet(this, null, ex)){ + cancelAll(); + drain(); + } + else if(error != ex) { + Operators.onErrorDropped(ex, actual.currentContext()); + } + } + + void onComplete() { + if(DONE.decrementAndGet(this) < 0){ + return; + } + drain(); + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + drainLoop(); + } + + void drainLoop() { + int missed = 1; + + MergeSequentialInner[] s = this.subscribers; + int n = s.length; + Subscriber a = this.actual; + + for (;;) { + + long r = requested; + long e = 0; + + middle: + while (e != r) { + if (cancelled) { + cleanup(); + return; + } + + Throwable ex = error; + if (ex != null) { + cleanup(); + a.onError(ex); + return; + } + + boolean d = done == 0; + + boolean empty = true; + + for (int i = 0; i < n; i++) { + MergeSequentialInner inner = s[i]; + + Queue q = inner.queue; + if (q != null) { + T v = q.poll(); + + if (v != null) { + empty = false; + a.onNext(v); + inner.requestOne(); + if (++e == r) { + break middle; + } + } + } + } + + if (d && empty) { + a.onComplete(); + return; + } + + if (empty) { + break; + } + } + + if (e == r) { + if (cancelled) { + cleanup(); + return; + } + + Throwable ex = error; + if (ex != null) { + cleanup(); + a.onError(ex); + return; + } + + boolean d = done == 0; + + boolean empty = true; + + for (int i = 0; i < n; i++) { + MergeSequentialInner inner = s[i]; + + Queue q = inner.queue; + if (q != null && !q.isEmpty()) { + empty = false; + break; + } + } + + if (d && empty) { + a.onComplete(); + return; + } + } + + if (e != 0 && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + int w = wip; + if (w == missed) { + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + } + + static final class MergeSequentialInner implements InnerConsumer { + + final MergeSequentialMain parent; + + final int prefetch; + + final int limit; + + long produced; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(MergeSequentialInner.class, Subscription.class, "s"); + + volatile Queue queue; + + volatile boolean done; + + MergeSequentialInner(MergeSequentialMain parent, int prefetch) { + this.parent = parent; + this.prefetch = prefetch ; + this.limit = Operators.unboundedOrLimit(prefetch); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.TERMINATED) return done; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public Context currentContext() { + return parent.actual.currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + @Override + public void onNext(T t) { + parent.onNext(this, t); + } + + @Override + public void onError(Throwable t) { + parent.onError(t); + } + + @Override + public void onComplete() { + parent.onComplete(); + } + + void requestOne() { + long p = produced + 1; + if (p == limit) { + produced = 0; + s.request(p); + } else { + produced = p; + } + } + + public void cancel() { + Operators.terminate(S, this); + } + + Queue getQueue(Supplier> queueSupplier) { + Queue q = queue; + if (q == null) { + q = queueSupplier.get(); + this.queue = q; + } + return q; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeSort.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeSort.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelMergeSort.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2016-2021 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.Arrays; +import java.util.Comparator; +import java.util.List; +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.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Given sorted rail sequences (according to the provided comparator) as List + * emit the smallest item from these parallel Lists to the Subscriber. + *

+ * It expects the source to emit exactly one list (which could be empty). + * + * @param the value type + */ +final class ParallelMergeSort extends Flux implements Scannable { + + final ParallelFlux> source; + + final Comparator comparator; + + ParallelMergeSort(ParallelFlux> source, + Comparator comparator) { + this.source = source; + this.comparator = comparator; + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public void subscribe(CoreSubscriber actual) { + MergeSortMain parent = + new MergeSortMain<>(actual, source.parallelism(), comparator); + actual.onSubscribe(parent); + + source.subscribe(parent.subscribers); + } + + @Override + @Nullable + 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; + + return null; + } + + static final class MergeSortMain implements InnerProducer { + + final MergeSortInner[] subscribers; + + final List[] lists; + + final int[] indexes; + + final Comparator comparator; + final CoreSubscriber actual; + + volatile int wip; + + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(MergeSortMain.class, "wip"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(MergeSortMain.class, + "requested"); + + volatile boolean cancelled; + + volatile int remaining; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater REMAINING = + AtomicIntegerFieldUpdater.newUpdater(MergeSortMain.class, + "remaining"); + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + ERROR = + AtomicReferenceFieldUpdater.newUpdater(MergeSortMain.class, + Throwable.class, + "error"); + + @SuppressWarnings("unchecked") + MergeSortMain(CoreSubscriber actual, + int n, + Comparator comparator) { + this.comparator = comparator; + this.actual = actual; + MergeSortInner[] s = new MergeSortInner[n]; + + for (int i = 0; i < n; i++) { + s[i] = new MergeSortInner<>(this, i); + } + this.subscribers = s; + this.lists = new List[n]; + this.indexes = new int[n]; + REMAINING.lazySet(this, n); + } + + @Override + public final CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ERROR) return error; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.BUFFERED) return subscribers.length - remaining; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + if (remaining == 0) { + drain(); + } + } + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + cancelAll(); + if (WIP.getAndIncrement(this) == 0) { + Arrays.fill(lists, null); + } + } + } + + void cancelAll() { + for (MergeSortInner s : subscribers) { + s.cancel(); + } + } + + void innerNext(List value, int index) { + lists[index] = value; + if (REMAINING.decrementAndGet(this) == 0) { + drain(); + } + } + + void innerError(Throwable ex) { + if(ERROR.compareAndSet(this, null, ex)){ + cancelAll(); + drain(); + } + else if(error != ex) { + Operators.onErrorDropped(ex, actual.currentContext()); + } + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + int missed = 1; + Subscriber a = actual; + List[] lists = this.lists; + int[] indexes = this.indexes; + int n = indexes.length; + + for (; ; ) { + + long r = requested; + long e = 0L; + + while (e != r) { + if (cancelled) { + Arrays.fill(lists, null); + return; + } + + Throwable ex = error; + if (ex != null) { + cancelAll(); + Arrays.fill(lists, null); + a.onError(ex); + return; + } + + T min = null; + int minIndex = -1; + + for (int i = 0; i < n; i++) { + List list = lists[i]; + int index = indexes[i]; + + if (list.size() != index) { + if (min == null) { + min = list.get(index); + minIndex = i; + } + else { + T b = list.get(index); + if (comparator.compare(min, b) > 0) { + min = b; + minIndex = i; + } + } + } + } + + if (min == null) { + Arrays.fill(lists, null); + a.onComplete(); + return; + } + + a.onNext(min); + + indexes[minIndex]++; + + e++; + } + + if (e == r) { //TODO investigate condition always true + if (cancelled) { + Arrays.fill(lists, null); + return; + } + + Throwable ex = error; + if (ex != null) { + cancelAll(); + Arrays.fill(lists, null); + a.onError(ex); + return; + } + + boolean empty = true; + + for (int i = 0; i < n; i++) { + if (indexes[i] != lists[i].size()) { + empty = false; + break; + } + } + + if (empty) { + Arrays.fill(lists, null); + a.onComplete(); + return; + } + } + + if (e != 0 && r != Long.MAX_VALUE) { + REQUESTED.addAndGet(this, -e); + } + + int w = wip; + if (w == missed) { + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } + else { + missed = w; + } + } + } + } + + static final class MergeSortInner implements InnerConsumer> { + + final MergeSortMain parent; + + final int index; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + S = + AtomicReferenceFieldUpdater.newUpdater(MergeSortInner.class, + Subscription.class, + "s"); + + MergeSortInner(MergeSortMain parent, int index) { + this.parent = parent; + this.index = index; + } + + @Override + public Context currentContext() { + return parent.actual.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(List t) { + parent.innerNext(t, index); + } + + @Override + public void onError(Throwable t) { + parent.innerError(t); + } + + @Override + public void onComplete() { + // ignored + } + + void cancel() { + Operators.terminate(S, this); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelPeek.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelPeek.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelPeek.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.util.annotation.Nullable; + +/** + * Execute a Consumer in each 'rail' for the current element passing through. + * + * @param the value type + */ +final class ParallelPeek extends ParallelFlux implements SignalPeek{ + + final ParallelFlux source; + + final Consumer onNext; + final Consumer onAfterNext; + final Consumer onError; + final Runnable onComplete; + final Runnable onAfterTerminated; + final Consumer onSubscribe; + final LongConsumer onRequest; + final Runnable onCancel; + + ParallelPeek(ParallelFlux source, + @Nullable Consumer onNext, + @Nullable Consumer onAfterNext, + @Nullable Consumer onError, + @Nullable Runnable onComplete, + @Nullable Runnable onAfterTerminated, + @Nullable Consumer onSubscribe, + @Nullable LongConsumer onRequest, + @Nullable Runnable onCancel + ) { + this.source = source; + + this.onNext = onNext; + this.onAfterNext = onAfterNext; + this.onError = onError; + this.onComplete = onComplete; + this.onAfterTerminated = onAfterTerminated; + this.onSubscribe = onSubscribe; + this.onRequest = onRequest; + this.onCancel = onCancel; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + CoreSubscriber[] parents = new CoreSubscriber[n]; + + boolean conditional = subscribers[0] instanceof Fuseable.ConditionalSubscriber; + + for (int i = 0; i < n; i++) { + if (conditional) { + parents[i] = new FluxPeekFuseable.PeekConditionalSubscriber<>( + (Fuseable.ConditionalSubscriber)subscribers[i], this); + } + else { + parents[i] = new FluxPeek.PeekSubscriber<>(subscribers[i], this); + } + } + + source.subscribe(parents); + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + @Override + public int getPrefetch() { + return source.getPrefetch(); + } + + @Override + @Nullable + public Consumer onSubscribeCall() { + return onSubscribe; + } + + @Override + @Nullable + public Consumer onNextCall() { + return onNext; + } + + @Override + @Nullable + public Consumer onErrorCall() { + return onError; + } + + @Override + @Nullable + public Runnable onCompleteCall() { + return onComplete; + } + + @Override + public Runnable onAfterTerminateCall() { + return onAfterTerminated; + } + + @Override + @Nullable + public LongConsumer onRequestCall() { + return onRequest; + } + + @Override + @Nullable + public Runnable onCancelCall() { + return onCancel; + } + + @Override + @Nullable + public Consumer onAfterNextCall() { + return onAfterNext; + } + + @Override + @Nullable + 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; + + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelReduceSeed.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelReduceSeed.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelReduceSeed.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2016-2021 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.BiFunction; +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; +import reactor.util.annotation.Nullable; + +/** + * Reduce the sequence of values in each 'rail' to a single value. + * + * @param the input value type + * @param the result value type + */ +final class ParallelReduceSeed extends ParallelFlux implements + Scannable, Fuseable { + + final ParallelFlux source; + + final Supplier initialSupplier; + + final BiFunction reducer; + + ParallelReduceSeed(ParallelFlux source, + Supplier initialSupplier, + BiFunction reducer) { + this.source = source; + this.initialSupplier = initialSupplier; + this.reducer = reducer; + } + + @Override + @Nullable + public Object scanUnsafe(Scannable.Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public int getPrefetch() { + return Integer.MAX_VALUE; + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + @SuppressWarnings("unchecked") CoreSubscriber[] parents = new CoreSubscriber[n]; + + for (int i = 0; i < n; i++) { + + R initialValue; + + try { + initialValue = Objects.requireNonNull(initialSupplier.get(), + "The initialSupplier returned a null value"); + } + catch (Throwable ex) { + reportError(subscribers, Operators.onOperatorError(ex, + subscribers[i].currentContext())); + return; + } + parents[i] = + new ParallelReduceSeedSubscriber<>(subscribers[i], initialValue, reducer); + } + + source.subscribe(parents); + } + + void reportError(Subscriber[] subscribers, Throwable ex) { + for (Subscriber s : subscribers) { + Operators.error(s, ex); + } + } + + @Override + public int parallelism() { + return source.parallelism(); + } + + + static final class ParallelReduceSeedSubscriber + extends Operators.MonoSubscriber { + + final BiFunction reducer; + + R accumulator; + + Subscription s; + + boolean done; + + ParallelReduceSeedSubscriber(CoreSubscriber subscriber, + R initialValue, + BiFunction reducer) { + super(subscriber); + this.accumulator = initialValue; + this.reducer = reducer; + } + + @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; + + 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; + } + + accumulator = v; + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, actual.currentContext()); + return; + } + done = true; + accumulator = null; + actual.onError(t); + } + + @Override + public void onComplete() { + if (done) { + return; + } + done = true; + + R a = accumulator; + accumulator = null; + complete(a); + } + + @Override + public void cancel() { + super.cancel(); + s.cancel(); + } + + @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/ParallelRunOn.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelRunOn.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelRunOn.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016-2021 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.Queue; +import java.util.function.Supplier; + +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Scheduler.Worker; +import reactor.util.annotation.Nullable; + +/** + * Ensures each 'rail' from upstream runs on a Worker from a Scheduler. + * + * @param the value type + */ +final class ParallelRunOn extends ParallelFlux implements Scannable{ + final ParallelFlux source; + + final Scheduler scheduler; + + final int prefetch; + + final Supplier> queueSupplier; + + ParallelRunOn(ParallelFlux parent, + Scheduler scheduler, int prefetch, Supplier> queueSupplier) { + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.source = parent; + this.scheduler = scheduler; + this.prefetch = prefetch; + this.queueSupplier = queueSupplier; + } + + @Override + @Nullable + 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.ASYNC; + + return null; + } + + @Override + @SuppressWarnings("unchecked") + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + int n = subscribers.length; + + + CoreSubscriber[] parents = new CoreSubscriber[n]; + + boolean conditional = subscribers[0] instanceof Fuseable.ConditionalSubscriber; + + for (int i = 0; i < n; i++) { + Worker w = scheduler.createWorker(); + + if (conditional) { + parents[i] = new FluxPublishOn.PublishOnConditionalSubscriber<>( + (Fuseable.ConditionalSubscriber)subscribers[i], + scheduler, w, true, + prefetch, prefetch, queueSupplier); + } + else { + parents[i] = new FluxPublishOn.PublishOnSubscriber<>(subscribers[i], + scheduler, w, true, + prefetch, prefetch, queueSupplier); + } + } + + source.subscribe(parents); + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public int parallelism() { + return source.parallelism(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelSource.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelSource.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelSource.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2016-2021 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.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongArray; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +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; + +/** + * Dispatches the values from upstream in a round robin fashion to subscribers which are + * ready to consume elements. A value from upstream is sent to only one of the subscribers. + * + * @param the value type + */ +final class ParallelSource extends ParallelFlux implements Scannable { + final Publisher source; + + final int parallelism; + + final int prefetch; + + final Supplier> queueSupplier; + + ParallelSource(Publisher source, int parallelism, int prefetch, Supplier> queueSupplier) { + if (parallelism <= 0) { + throw new IllegalArgumentException("parallelism > 0 required but it was " + parallelism); + } + if (prefetch <= 0) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + this.source = source; + this.parallelism = parallelism; + this.prefetch = prefetch; + this.queueSupplier = queueSupplier; + } + + @Override + public int getPrefetch() { + return prefetch; + } + + @Override + public int parallelism() { + return parallelism; + } + + @Override + @Nullable + public Object scanUnsafe(Scannable.Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.PREFETCH) return getPrefetch(); + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void subscribe(CoreSubscriber[] subscribers) { + if (!validate(subscribers)) { + return; + } + + source.subscribe(new ParallelSourceMain<>(subscribers, prefetch, queueSupplier)); + } + + static final class ParallelSourceMain implements InnerConsumer { + + final CoreSubscriber[] subscribers; + + final AtomicLongArray requests; + + final long[] emissions; + + final int prefetch; + + final int limit; + + final Supplier> queueSupplier; + + Subscription s; + + Queue queue; + + Throwable error; + + volatile boolean done; + + int index; + + volatile boolean cancelled; + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ParallelSourceMain.class, "wip"); + + /** + * Counts how many subscribers were setup to delay triggering the + * drain of upstream until all of them have been setup. + */ + volatile int subscriberCount; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater SUBSCRIBER_COUNT = + AtomicIntegerFieldUpdater.newUpdater(ParallelSourceMain.class, "subscriberCount"); + + int produced; + + int sourceMode; + + ParallelSourceMain(CoreSubscriber[] subscribers, int prefetch, + Supplier> queueSupplier) { + this.subscribers = subscribers; + this.prefetch = prefetch; + this.queueSupplier = queueSupplier; + this.limit = Operators.unboundedOrLimit(prefetch); + this.requests = new AtomicLongArray(subscribers.length); + this.emissions = new long[subscribers.length]; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.PREFETCH) return prefetch; + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.ERROR) return error; + if (key == Attr.BUFFERED) return queue != null ? queue.size() : 0; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public Stream inners() { + return Stream.of(subscribers).map(Scannable::from); + } + + @Override + public Context currentContext() { + return subscribers[0].currentContext(); + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + this.s = s; + + if (s instanceof Fuseable.QueueSubscription) { + @SuppressWarnings("unchecked") + Fuseable.QueueSubscription qs = (Fuseable.QueueSubscription) s; + + int m = qs.requestFusion(Fuseable.ANY | Fuseable.THREAD_BARRIER); + + if (m == Fuseable.SYNC) { + sourceMode = m; + queue = qs; + done = true; + setupSubscribers(); + drain(); + return; + } else + if (m == Fuseable.ASYNC) { + sourceMode = m; + queue = qs; + + setupSubscribers(); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + + return; + } + } + + queue = queueSupplier.get(); + + setupSubscribers(); + + s.request(Operators.unboundedOrPrefetch(prefetch)); + } + } + + void setupSubscribers() { + int m = subscribers.length; + + for (int i = 0; i < m; i++) { + if (cancelled) { + return; + } + int j = i; + + SUBSCRIBER_COUNT.lazySet(this, i + 1); + + subscribers[i].onSubscribe(new ParallelSourceInner<>(this, j, m)); + } + } + + @Override + public void onNext(T t) { + if (done) { + Operators.onNextDropped(t, currentContext()); + return; + } + if (sourceMode == Fuseable.NONE) { + if (!queue.offer(t)) { + onError(Operators.onOperatorError(s, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t, currentContext())); + return; + } + } + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + Operators.onErrorDropped(t, currentContext()); + return; + } + error = t; + done = true; + drain(); + } + + @Override + public void onComplete() { + if(done){ + return; + } + done = true; + drain(); + } + + void cancel() { + if (!cancelled) { + cancelled = true; + this.s.cancel(); + + if (WIP.getAndIncrement(this) == 0) { + queue.clear(); + } + } + } + + void drainAsync() { + int missed = 1; + + Queue q = queue; + CoreSubscriber[] a = this.subscribers; + AtomicLongArray r = this.requests; + long[] e = this.emissions; + int n = e.length; + int idx = index; + int consumed = produced; + + for (;;) { + + int notReady = 0; + + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + if (d) { + Throwable ex = error; + if (ex != null) { + q.clear(); + for (Subscriber s : a) { + s.onError(ex); + } + return; + } + } + + boolean empty = q.isEmpty(); + + if (d && empty) { + for (Subscriber s : a) { + s.onComplete(); + } + return; + } + + if (empty) { + break; + } + + long ridx = r.get(idx); + long eidx = e[idx]; + if (ridx != eidx) { + + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + ex = Operators.onOperatorError(s, ex, a[idx].currentContext()); + for (Subscriber s : a) { + s.onError(ex); + } + return; + } + + if (v == null) { + break; + } + + a[idx].onNext(v); + + e[idx] = eidx + 1; + + int c = ++consumed; + if (c == limit) { + consumed = 0; + s.request(c); + } + notReady = 0; + } else { + notReady++; + } + + idx++; + if (idx == n) { + idx = 0; + } + + if (notReady == n) { + break; + } + } + + int w = wip; + if (w == missed) { + index = idx; + produced = consumed; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + + void drainSync() { + int missed = 1; + + Queue q = queue; + CoreSubscriber[] a = this.subscribers; + AtomicLongArray r = this.requests; + long[] e = this.emissions; + int n = e.length; + int idx = index; + + for (;;) { + + int notReady = 0; + + for (;;) { + if (cancelled) { + q.clear(); + return; + } + + if (q.isEmpty()) { + for (Subscriber s : a) { + s.onComplete(); + } + return; + } + + long ridx = r.get(idx); + long eidx = e[idx]; + if (ridx != eidx) { + + T v; + + try { + v = q.poll(); + } catch (Throwable ex) { + ex = Operators.onOperatorError(s, ex, a[idx].currentContext()); + for (Subscriber s : a) { + s.onError(ex); + } + return; + } + + if (v == null) { + for (Subscriber s : a) { + s.onComplete(); + } + return; + } + + a[idx].onNext(v); + + e[idx] = eidx + 1; + + notReady = 0; + } else { + notReady++; + } + + idx++; + if (idx == n) { + idx = 0; + } + + if (notReady == n) { + break; + } + } + + int w = wip; + if (w == missed) { + index = idx; + missed = WIP.addAndGet(this, -missed); + if (missed == 0) { + break; + } + } else { + missed = w; + } + } + } + + void drain() { + if (WIP.getAndIncrement(this) != 0) { + return; + } + + if (sourceMode == Fuseable.SYNC) { + drainSync(); + } else { + drainAsync(); + } + } + + static final class ParallelSourceInner implements InnerProducer { + + final ParallelSourceMain parent; + + final int index; + final int length; + + ParallelSourceInner(ParallelSourceMain parent, int index, int length) { + this.index = index; + this.length = length; + this.parent = parent; + } + + @Override + public CoreSubscriber actual() { + return parent.subscribers[index]; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + AtomicLongArray ra = parent.requests; + for (;;) { + long r = ra.get(index); + if (r == Long.MAX_VALUE) { + return; + } + long u = Operators.addCap(r, n); + if (ra.compareAndSet(index, r, u)) { + break; + } + } + if (parent.subscriberCount == length) { + parent.drain(); + } + } + } + + @Override + public void cancel() { + parent.cancel(); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/ParallelThen.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ParallelThen.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ParallelThen.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2019-2021 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.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import org.reactivestreams.Subscription; +import reactor.core.CoreSubscriber; +import reactor.core.Fuseable; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Observe termination from all 'rails' into a single Mono. + */ +final class ParallelThen extends Mono implements Scannable, Fuseable { + + final ParallelFlux source; + + ParallelThen(ParallelFlux source) { + this.source = source; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return source; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void subscribe(CoreSubscriber actual) { + ThenMain parent = new ThenMain(actual, source.parallelism()); + actual.onSubscribe(parent); + + source.subscribe(parent.subscribers); + } + + static final class ThenMain + extends Operators.MonoSubscriber { + + final ThenInner[] subscribers; + + volatile int remaining; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater + REMAINING = AtomicIntegerFieldUpdater.newUpdater( + ThenMain.class, + "remaining"); + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + ERROR = AtomicReferenceFieldUpdater.newUpdater( + ThenMain.class, + Throwable.class, + "error"); + + ThenMain(CoreSubscriber subscriber, int n) { + super(subscriber); + 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 + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.ERROR) return error; + if (key == Attr.TERMINATED) return REMAINING.get(this) == 0; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return super.scanUnsafe(key); + } + + @Override + public void cancel() { + for (ThenInner inner : subscribers) { + inner.cancel(); + } + super.cancel(); + } + + void innerError(Throwable ex) { + if(ERROR.compareAndSet(this, null, ex)){ + cancel(); + actual.onError(ex); + } + else if(error != ex) { + Operators.onErrorDropped(ex, actual.currentContext()); + } + } + + void innerComplete() { + if (REMAINING.decrementAndGet(this) == 0) { + actual.onComplete(); + } + } + } + + static final class ThenInner implements InnerConsumer { + + final ThenMain parent; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater + S = AtomicReferenceFieldUpdater.newUpdater( + ThenInner.class, + Subscription.class, + "s"); + + ThenInner(ThenMain parent) { + this.parent = parent; + } + + @Override + public Context currentContext() { + return parent.currentContext(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.CANCELLED) return s == Operators.cancelledSubscription(); + if (key == Attr.PARENT) return s; + if (key == Attr.ACTUAL) return parent; + if (key == Attr.PREFETCH) return Integer.MAX_VALUE; + if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.setOnce(S, this, s)) { + s.request(Long.MAX_VALUE); + } + } + + @Override + public void onNext(Object t) { + //ignored + Operators.onDiscard(t, parent.currentContext()); + } + + @Override + public void onError(Throwable t) { + parent.innerError(t); + } + + @Override + public void onComplete() { + parent.innerComplete(); + } + + void cancel() { + Operators.terminate(S, this); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/QueueDrainSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/QueueDrainSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/QueueDrainSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2018-2021 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.Queue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; + +import org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.util.annotation.Nullable; + +/** + * Abstract base class for subscribers that hold another subscriber, a queue + * and requires queue-drain behavior. + * + * @param the source type to which this subscriber will be subscribed + * @param the value type in the queue + * @param the value type the child subscriber accepts + * + * @author Simon Baslé + * @author David Karnok + */ +abstract class QueueDrainSubscriber extends QueueDrainSubscriberPad4 + implements InnerOperator { + + final CoreSubscriber actual; + final Queue queue; + + volatile boolean cancelled; + + volatile boolean done; + Throwable error; + + QueueDrainSubscriber(CoreSubscriber actual, Queue queue) { + this.actual = actual; + this.queue = queue; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + public final boolean cancelled() { + return cancelled; + } + + public final boolean done() { + return done; + } + + public final boolean enter() { + return wip.getAndIncrement() == 0; + } + + public final boolean fastEnter() { + return wip.get() == 0 && wip.compareAndSet(0, 1); + } + + protected final void fastPathEmitMax(U value, boolean delayError, Disposable dispose) { + final Subscriber s = actual; + final Queue q = queue; + + if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + long r = requested; + if (r != 0L) { + if (accept(s, value)) { + if (r != Long.MAX_VALUE) { + produced(1); + } + } + if (leave(-1) == 0) { + return; + } + } else { + dispose.dispose(); + s.onError(Exceptions.failWithOverflow("Could not emit buffer due to lack of requests")); + return; + } + } else { + q.offer(value); + if (!enter()) { + return; + } + } + drainMaxLoop(q, s, delayError, dispose, this); + } + + protected final void fastPathOrderedEmitMax(U value, boolean delayError, Disposable dispose) { + final Subscriber s = actual; + final Queue q = queue; + + if (wip.get() == 0 && wip.compareAndSet(0, 1)) { + long r = requested; + if (r != 0L) { + if (q.isEmpty()) { + if (accept(s, value)) { + if (r != Long.MAX_VALUE) { + produced(1); + } + } + if (leave(-1) == 0) { + return; + } + } else { + q.offer(value); + } + } else { + cancelled = true; + dispose.dispose(); + s.onError(Exceptions.failWithOverflow("Could not emit buffer due to lack of requests")); + return; + } + } else { + q.offer(value); + if (!enter()) { + return; + } + } + drainMaxLoop(q, s, delayError, dispose, this); + } + + /** + * Accept the value and return true if forwarded. + * @param a the subscriber + * @param v the value + * @return true if the value was delivered + */ + public boolean accept(Subscriber a, U v) { + return false; + } + + public final Throwable error() { + return error; + } + + /** + * Adds m to the wip counter. + * @param m the value to add + * @return the current value after adding m + */ + public final int leave(int m) { + return wip.addAndGet(m); + } + + public final long requested() { + return requested; + } + + public final long produced(long n) { + return REQUESTED.addAndGet(this, -n); + } + + public final void requested(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return done; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested; + if (key == Attr.ERROR) return error; + + return InnerOperator.super.scanUnsafe(key); + } + + /** + * Drain the queue but give up with an error if there aren't enough requests. + * @param the queue value type + * @param the emission value type + * @param q the queue + * @param a the subscriber + * @param delayError true if errors should be delayed after all normal items + * @param dispose the disposable to call when termination happens and cleanup is necessary + * @param qd the QueueDrain instance that gives status information to the drain logic + */ + static void drainMaxLoop(Queue q, Subscriber a, boolean delayError, + Disposable dispose, QueueDrainSubscriber qd) { + int missed = 1; + + for (;;) { + for (;;) { + boolean d = qd.done(); + + Q v = q.poll(); + + boolean empty = v == null; + + if (checkTerminated(d, empty, a, delayError, q, qd)) { + if (dispose != null) { + dispose.dispose(); + } + return; + } + + if (empty) { + break; + } + + long r = qd.requested(); + if (r != 0L) { + if (qd.accept(a, v)) { + if (r != Long.MAX_VALUE) { + qd.produced(1); + } + } + } else { + q.clear(); + if (dispose != null) { + dispose.dispose(); + } + a.onError(Exceptions.failWithOverflow("Could not emit value due to lack of requests.")); + return; + } + } + + missed = qd.leave(-missed); + if (missed == 0) { + break; + } + } + } + + static boolean checkTerminated(boolean d, boolean empty, + Subscriber s, boolean delayError, Queue q, QueueDrainSubscriber qd) { + if (qd.cancelled()) { + q.clear(); + return true; + } + + if (d) { + if (delayError) { + if (empty) { + Throwable err = qd.error(); + if (err != null) { + s.onError(err); + } else { + s.onComplete(); + } + return true; + } + } else { + Throwable err = qd.error(); + if (err != null) { + q.clear(); + s.onError(err); + return true; + } else + if (empty) { + s.onComplete(); + return true; + } + } + } + + return false; + } + +} + +// ------------------------------------------------------------------- +// Padding superclasses +//------------------------------------------------------------------- + +/** Pads the header away from other fields. */ +class QueueDrainSubscriberPad0 { + volatile long p1, p2, p3, p4, p5, p6, p7; + volatile long p8, p9, p10, p11, p12, p13, p14, p15; +} + +/** The WIP counter. */ +class QueueDrainSubscriberWip extends QueueDrainSubscriberPad0 { + final AtomicInteger wip = new AtomicInteger(); +} + +/** Pads away the wip from the other fields. */ +class QueueDrainSubscriberPad2 extends QueueDrainSubscriberWip { + volatile long p1a, p2a, p3a, p4a, p5a, p6a, p7a; + volatile long p8a, p9a, p10a, p11a, p12a, p13a, p14a, p15a; +} + +/** Contains the requested field. */ +class QueueDrainSubscriberPad3 extends QueueDrainSubscriberPad2 { + + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(QueueDrainSubscriberPad3.class, "requested"); + + volatile long requested; +} + +/** Pads away the requested from the other fields. */ +class QueueDrainSubscriberPad4 extends QueueDrainSubscriberPad3 { + volatile long q1, q2, q3, q4, q5, q6, q7; + volatile long q8, q9, q10, q11, q12, q13, q14, q15; +} \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/publisher/ReplayProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/ReplayProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/ReplayProcessor.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2016-2021 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.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.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 + * @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()}. + */ +@Deprecated +public final class ReplayProcessor extends FluxProcessor + implements Fuseable, InternalManySink { + + /** + * Create a {@link ReplayProcessor} that caches the last element it has pushed, + * replaying it to late subscribers. This is a buffer-based ReplayProcessor with + * a history size of 1. + *

+ * + * + * @param the type of the pushed elements + * + * @return a new {@link ReplayProcessor} that replays its last pushed element to each new + * {@link Subscriber} + * @deprecated use {@link Sinks.MulticastReplaySpec#latest() Sinks.many().replay().latest()} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static ReplayProcessor cacheLast() { + return cacheLastOrDefault(null); + } + + /** + * Create a {@link ReplayProcessor} 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 ReplayProcessor 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 ReplayProcessor} that replays its last pushed element to each new + * {@link Subscriber}, or a default one if nothing was pushed yet + * @deprecated use {@link Sinks.MulticastReplaySpec#latestOrDefault(Object) Sinks.many().replay().latestOrDefault(value)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static ReplayProcessor cacheLastOrDefault(@Nullable T value) { + ReplayProcessor b = create(1); + if (value != null) { + b.onNext(value); + } + return b; + } + + /** + * Create a new {@link ReplayProcessor} 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 ReplayProcessor} that replays the whole history to each new + * {@link Subscriber}. + * @deprecated use {@link Sinks.MulticastReplaySpec#all() Sinks.many().replay().all()} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static ReplayProcessor create() { + return create(Queues.SMALL_BUFFER_SIZE, true); + } + + /** + * Create a new {@link ReplayProcessor} 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 ReplayProcessor} that replays a limited history to each new + * {@link Subscriber}. + * @deprecated use {@link Sinks.MulticastReplaySpec#limit(int) Sinks.many().replay().limit(historySize)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static ReplayProcessor create(int historySize) { + return create(historySize, false); + } + + /** + * Create a new {@link ReplayProcessor} 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 ReplayProcessor} that replays the whole history to each new + * {@link Subscriber} if configured as unbounded, a limited history otherwise. + * @deprecated use {@link Sinks.MulticastReplaySpec#limit(int) Sinks.many().replay().limit(historySize)} + * for bounded cases ({@code unbounded == false}) or {@link Sinks.MulticastReplaySpec#all(int) Sinks.many().replay().all(bufferSize)} + * otherwise (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static ReplayProcessor create(int historySize, boolean unbounded) { + FluxReplay.ReplayBuffer buffer; + if (unbounded) { + buffer = new FluxReplay.UnboundedReplayBuffer<>(historySize); + } + else { + buffer = new FluxReplay.SizeBoundReplayBuffer<>(historySize); + } + return new ReplayProcessor<>(buffer); + } + + /** + * Creates a time-bounded replay processor. + *

+ * In this setting, the {@code ReplayProcessor} 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 ReplayProcessor} 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 Processor + * @param maxAge the maximum age of the contained items + * + * @return a new {@link ReplayProcessor} that replays elements based on their age. + * @deprecated use {@link Sinks.MulticastReplaySpec#limit(Duration) Sinks.many().replay().limit(maxAge)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static ReplayProcessor createTimeout(Duration maxAge) { + return createTimeout(maxAge, Schedulers.parallel()); + } + + /** + * Creates a time-bounded replay processor. + *

+ * In this setting, the {@code ReplayProcessor} 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 ReplayProcessor} 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 Processor + * @param maxAge the maximum age of the contained items + * + * @return a new {@link ReplayProcessor} that replays elements based on their age. + * @deprecated use {@link Sinks.MulticastReplaySpec#limit(Duration, Scheduler) Sinks.many().replay().limit(maxAge, scheduler)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static ReplayProcessor 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 ReplayProcessor} 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 ReplayProcessor}, 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 ReplayProcessor} 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 Processor + * @param maxAge the maximum age of the contained items + * @param size the maximum number of buffered items + * + * @return a new {@link ReplayProcessor} that replay up to {@code size} elements, but + * will evict them from its history based on their age. + * @deprecated use {@link Sinks.MulticastReplaySpec#limit(int, Duration) Sinks.many().replay().limit(size, maxAge)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static ReplayProcessor createSizeAndTimeout(int size, Duration maxAge) { + return createSizeAndTimeout(size, maxAge, Schedulers.parallel()); + } + + /** + * Creates a time- and size-bounded replay processor. + *

+ * In this setting, the {@code ReplayProcessor} 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 ReplayProcessor}, 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 ReplayProcessor} 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 Processor + * @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 ReplayProcessor} that replay up to {@code size} elements, but + * will evict them from its history based on their age. + * @deprecated use {@link Sinks.MulticastReplaySpec#limit(int, Duration, Scheduler) Sinks.many().replay().limit(size, maxAge, scheduler)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static ReplayProcessor 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 ReplayProcessor<>(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(ReplayProcessor.class, + FluxReplay.ReplaySubscription[].class, + "subscribers"); + + ReplayProcessor(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 Throwable getError() { + return buffer.getError(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT){ + return subscription; + } + if (key == Attr.CAPACITY) return buffer.capacity(); + + return super.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Override + public long downstreamCount() { + return subscribers.length; + } + + @Override + public boolean isTerminated() { + 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") Sinks.EmitResult emitResult = tryEmitComplete(); + } + + @Override + public Sinks.EmitResult tryEmitComplete() { + FluxReplay.ReplayBuffer b = buffer; + if (b.isDone()) { + return Sinks.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 Sinks.EmitResult tryEmitNext(T t) { + FluxReplay.ReplayBuffer b = buffer; + if (b.isDone()) { + return Sinks.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; + } + + @Override + public int currentSubscriberCount() { + return subscribers.length; + } + + @Override + public Flux asFlux() { + return this; + } + + @Override + protected boolean isIdentityProcessor() { + return true; + } + + static final class ReplayInner + implements FluxReplay.ReplaySubscription { + + final CoreSubscriber actual; + + final ReplayProcessor 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, + ReplayProcessor 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/SerializedSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SerializedSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SerializedSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2016-2021 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.util.annotation.Nullable; + +/** + * Subscriber that makes sure signals are delivered sequentially in case the onNext, onError or onComplete methods are + * called concurrently. + *

+ *

+ * The implementation uses {@code synchronized (this)} to ensure mutual exclusion. + *

+ *

+ * Note that the class implements Subscription to save on allocation. + * + * @param the value type + */ +final class SerializedSubscriber implements InnerOperator { + + final CoreSubscriber actual; + + boolean drainLoopInProgress; + + boolean concurrentlyAddedContent; + + volatile boolean done; + + volatile boolean cancelled; + + LinkedArrayNode head; + + LinkedArrayNode tail; + + Throwable error; + + Subscription s; + + SerializedSubscriber(CoreSubscriber actual) { + this.actual = actual; + } + + @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 (cancelled) { + Operators.onDiscard(t, actual.currentContext()); + return; + } + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + synchronized (this) { + if (cancelled) { + Operators.onDiscard(t, actual.currentContext()); + return; + } + if (done) { + Operators.onNextDropped(t, actual.currentContext()); + return; + } + + if (drainLoopInProgress) { + serAdd(t); + concurrentlyAddedContent = true; + return; + } + + drainLoopInProgress = true; + } + + actual.onNext(t); + + serDrainLoop(actual); + } + + @Override + public void onError(Throwable t) { + if (cancelled || done) { + return; + } + + synchronized (this) { + if (cancelled || done) { + return; + } + + done = true; + error = t; + + if (drainLoopInProgress) { + concurrentlyAddedContent = true; + return; + } + } + + actual.onError(t); + } + + @Override + public void onComplete() { + if (cancelled || done) { + return; + } + + synchronized (this) { + if (cancelled || done) { + return; + } + + done = true; + + if (drainLoopInProgress) { + concurrentlyAddedContent = true; + return; + } + } + + actual.onComplete(); + } + + @Override + public void request(long n) { + s.request(n); + } + + @Override + public void cancel() { + cancelled = true; + s.cancel(); + } + + void serAdd(T value) { + if (cancelled) { + Operators.onDiscard(value, actual.currentContext()); + return; + } + + LinkedArrayNode t = tail; + + if (t == null) { + t = new LinkedArrayNode<>(value); + + head = t; + tail = t; + } + else { + if (t.count == LinkedArrayNode.DEFAULT_CAPACITY) { + LinkedArrayNode n = new LinkedArrayNode<>(value); + + t.next = n; + tail = n ; + } + else { + t.array[t.count++] = value; + } + } + if (cancelled) { + //this case could mean serDrainLoop "won" and cleared an old view of the nodes first, + // then we added "from scratch", so we remain with a single-element node containing `value` + // => we can simply discard `value` + Operators.onDiscard(value, actual.currentContext()); + } + } + + void serDrainLoop(CoreSubscriber actual) { + for (; ; ) { + + if (cancelled) { + synchronized (this) { + discardMultiple(this.head); + } + return; + } + + boolean d; + Throwable e; + LinkedArrayNode n; + + synchronized (this) { + if (cancelled) { + discardMultiple(this.head); + return; + } + + if (!concurrentlyAddedContent) { + drainLoopInProgress = false; + return; + } + + concurrentlyAddedContent = false; + + d = done; + e = error; + n = head; + + head = null; + tail = null; + } + + while (n != null) { + + T[] arr = n.array; + int c = n.count; + + for (int i = 0; i < c; i++) { + + if (cancelled) { + synchronized (this) { + discardMultiple(n); + } + return; + } + + actual.onNext(arr[i]); + } + + n = n.next; + } + + if (cancelled) { + synchronized (this) { + discardMultiple(this.head); + } + return; + } + + if (e != null) { + actual.onError(e); + return; + } + else if (d) { + actual.onComplete(); + return; + } + } + } + + private void discardMultiple(LinkedArrayNode head) { + LinkedArrayNode originalHead = head; + LinkedArrayNode h = head; + while (h != null) { + //discard + for (int i = 0; i < h.count; i++) { + Operators.onDiscard(h.array[i], actual.currentContext()); + } + h = h.next; + + if (h == null && this.head != originalHead) { + originalHead = this.head; + h = originalHead; + } + } + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return s; + if (key == Attr.ERROR) return error; + if (key == Attr.BUFFERED) return producerCapacity(); + if (key == Attr.CAPACITY) return LinkedArrayNode.DEFAULT_CAPACITY; + if (key == Attr.CANCELLED) return cancelled; + if (key == Attr.TERMINATED) return done; + + return InnerOperator.super.scanUnsafe(key); + } + + int producerCapacity() { + LinkedArrayNode node = tail; + if(node != null){ + return node.count; + } + return 0; + } + + /** + * Node in a linked array list that is only appended. + * + * @param the value type + */ + static final class LinkedArrayNode { + + static final int DEFAULT_CAPACITY = 16; + + final T[] array; + int count; + + LinkedArrayNode next; + + @SuppressWarnings("unchecked") + LinkedArrayNode(T value) { + array = (T[]) new Object[DEFAULT_CAPACITY]; + array[0] = value; + count = 1; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/Signal.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/Signal.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/Signal.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; +import java.util.function.Supplier; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +/** + * A domain representation of a Reactive Stream signal. + * There are 4 distinct signals and their possible sequence is defined as such: + * onError | (onSubscribe onNext* (onError | onComplete)?) + * + * @param the value type + * + * @author Stephane Maldini + */ +public interface Signal extends Supplier, Consumer> { + + /** + * Creates and returns a {@code Signal} of variety {@code Type.COMPLETE}. + *

+ * Note that this variant associates an empty {@link Context} with the {@link Signal}. + * + * @param the value type + * + * @return an {@code OnCompleted} variety of {@code Signal} + */ + static Signal complete() { + return ImmutableSignal.onComplete(); + } + + /** + * Creates and returns a {@code Signal} of variety {@code Type.COMPLETE}, associated + * with a specific {@link Context}. + * + * @param the value type + * @param context the {@link Context} associated with the completing source. + * + * @return an {@code OnCompleted} variety of {@code Signal} + */ + static Signal complete(Context context) { + if (context.isEmpty()) { + return ImmutableSignal.onComplete(); + } + return new ImmutableSignal<>(context, SignalType.ON_COMPLETE, null, null, null); + } + + /** + * Creates and returns a {@code Signal} of variety {@code Type.FAILED}, which holds + * the error. + *

+ * Note that this variant associates an empty {@link Context} with the {@link Signal}. + * + * @param the value type + * @param e the error associated to the signal + * + * @return an {@code OnError} variety of {@code Signal} + */ + static Signal error(Throwable e) { + return error(e, Context.empty()); + } + + /** + * Creates and returns a {@code Signal} of variety {@code Type.FAILED}, which holds + * the error and the {@link Context} associated with the erroring source. + * + * @param the value type + * @param e the error associated to the signal + * @param context the {@link Context} associated with the erroring source + * + * @return an {@code OnError} variety of {@code Signal} + */ + static Signal error(Throwable e, Context context) { + return new ImmutableSignal<>(context, SignalType.ON_ERROR, null, e, null); + } + + /** + * Creates and returns a {@code Signal} of variety {@code Type.NEXT}, which holds + * the value. + *

+ * Note that this variant associates an empty {@link Context} with the {@link Signal}. + * + * @param the value type + * @param t the value item associated to the signal + * + * @return an {@code OnNext} variety of {@code Signal} + */ + static Signal next(T t) { + return next(t, Context.empty()); + } + + /** + * Creates and returns a {@code Signal} of variety {@code Type.NEXT}, which holds + * the value and the {@link Context} associated with the emitting source. + * + * @param the value type + * @param t the value item associated to the signal + * @param context the {@link Context} associated with the emitting source + * + * @return an {@code OnNext} variety of {@code Signal} + */ + static Signal next(T t, Context context) { + return new ImmutableSignal<>(context, SignalType.ON_NEXT, t, null, null); + } + + /** + * Creates and returns a {@code Signal} of variety {@code Type.ON_SUBSCRIBE}. + *

+ * Note that this variant associates an empty {@link Context} with the {@link Signal}. + * + * @param the value type + * @param subscription the subscription + * + * @return an {@code OnSubscribe} variety of {@code Signal} + */ + static Signal subscribe(Subscription subscription) { + return subscribe(subscription, Context.empty()); + } + + /** + * Creates and returns a {@code Signal} of variety {@code Type.ON_SUBSCRIBE}, that + * holds the {@link Context} associated with the subscribed source. + * + * @param the value type + * @param subscription the subscription + * @param context the {@link Context} associated with the subscribed source + * + * @return an {@code OnSubscribe} variety of {@code Signal} + */ + static Signal subscribe(Subscription subscription, Context context) { + return new ImmutableSignal<>(context, SignalType.ON_SUBSCRIBE, null, null, subscription); + } + + /** + * Check if an arbitrary Object represents a COMPLETE {@link Signal}. + * + * @param o the object to check + * @return true if object represents the completion signal + */ + static boolean isComplete(Object o) { + return o == ImmutableSignal.onComplete() || + (o instanceof Signal && ((Signal) o).getType() == SignalType.ON_COMPLETE); + } + + /** + * Check if a arbitrary Object represents an ERROR {@link Signal}. + * + * @param o the object to check + * @return true if object represents the error signal + */ + static boolean isError(Object o) { + return o instanceof Signal && ((Signal) o).getType() == SignalType.ON_ERROR; + } + + /** + * Read the error associated with this (onError) signal. + * + * @return the Throwable associated with this (onError) signal, or null if not relevant + */ + @Nullable + Throwable getThrowable(); + + /** + * Read the subscription associated with this (onSubscribe) signal. + * + * @return the Subscription associated with this (onSubscribe) signal, or null if not + * relevant + */ + @Nullable + Subscription getSubscription(); + + /** + * Retrieves the item associated with this (onNext) signal. + * + * @return the item associated with this (onNext) signal, or null if not relevant + */ + @Override + @Nullable + T get(); + + /** + * Has this signal an item associated with it ? (which only happens if it is an + * (onNext) signal) + * + * @return a boolean indicating whether or not this signal has an item associated with + * it + */ + default boolean hasValue() { + return isOnNext() && get() != null; + } + + /** + * Read whether this signal is on error and carries the cause. + * + * @return a boolean indicating whether this signal has an error + */ + default boolean hasError() { + return isOnError() && getThrowable() != null; + } + + /** + * Read the type of this signal: {@link SignalType#ON_SUBSCRIBE}, + * {@link SignalType#ON_NEXT}, {@link SignalType#ON_ERROR}, or + * {@link SignalType#ON_COMPLETE} + * + * @return the type of the signal + */ + 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. + * + * @return a readonly {@link ContextView}, or an empty one if no context is available. + */ + ContextView getContextView(); + + /** + * Indicates whether this signal represents an {@code onError} event. + * + * @return a boolean indicating whether this signal represents an {@code onError} + * event + */ + default boolean isOnError() { + return getType() == SignalType.ON_ERROR; + } + + /** + * Indicates whether this signal represents an {@code onComplete} event. + * + * @return a boolean indicating whether this signal represents an {@code onComplete} + * event + */ + default boolean isOnComplete() { + return getType() == SignalType.ON_COMPLETE; + } + + /** + * Indicates whether this signal represents an {@code onSubscribe} event. + * + * @return a boolean indicating whether this signal represents an {@code onSubscribe} + * event + */ + default boolean isOnSubscribe() { + return getType() == SignalType.ON_SUBSCRIBE; + } + + /** + * Indicates whether this signal represents an {@code onNext} event. + * + * @return a boolean indicating whether this signal represents an {@code onNext} event + */ + default boolean isOnNext() { + return getType() == SignalType.ON_NEXT; + } + + /** + * Propagate the signal represented by this {@link Signal} instance to a + * given {@link Subscriber}. + * + * @param observer the {@link Subscriber} to play the {@link Signal} on + */ + @Override + default void accept(Subscriber observer) { + if (isOnNext()) { + observer.onNext(get()); + } + else if (isOnComplete()) { + observer.onComplete(); + } + else if (isOnError()) { + observer.onError(getThrowable()); + } + else if (isOnSubscribe()) { + observer.onSubscribe(getSubscription()); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SignalLogger.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SignalLogger.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SignalLogger.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2016-2021 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.AtomicLong; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.LongConsumer; +import java.util.logging.Level; + +import org.reactivestreams.Subscription; + +import reactor.core.CorePublisher; +import reactor.core.Fuseable; +import reactor.util.Logger; +import reactor.util.Loggers; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * A logging interceptor that intercepts all reactive calls and trace them. + * The logging level can be tuned using {@link Level}, but only FINEST, FINE, INFO, + * WARNING and SEVERE are taken into account. + * + * @author Stephane Maldini + */ +final class SignalLogger implements SignalPeek { + + final static int CONTEXT_PARENT = 0b0100000000; + final static int SUBSCRIBE = 0b0010000000; + final static int ON_SUBSCRIBE = 0b0001000000; + final static int ON_NEXT = 0b0000100000; + final static int ON_ERROR = 0b0000010000; + final static int ON_COMPLETE = 0b0000001000; + final static int REQUEST = 0b0000000100; + final static int CANCEL = 0b0000000010; + final static int AFTER_TERMINATE = 0b0000000001; + final static int ALL = + CONTEXT_PARENT | CANCEL | ON_COMPLETE | ON_ERROR | REQUEST | ON_SUBSCRIBE | ON_NEXT | SUBSCRIBE; + + final static AtomicLong IDS = new AtomicLong(1); + + final CorePublisher source; + + final Logger log; + final boolean fuseable; + final int options; + final Level level; + final String operatorLine; + final long id; + + static final String LOG_TEMPLATE = "{}({})"; + static final String LOG_TEMPLATE_FUSEABLE = "| {}({})"; + + SignalLogger(CorePublisher source, + @Nullable String category, + Level level, + boolean correlateStack, + SignalType... options) { + this(source, category, level, correlateStack, Loggers::getLogger, options); + } + + SignalLogger(CorePublisher source, + @Nullable String category, + Level level, + boolean correlateStack, + Function loggerSupplier, + @Nullable SignalType... options) { + + this.source = Objects.requireNonNull(source, "source"); + this.id = IDS.getAndIncrement(); + this.fuseable = source instanceof Fuseable; + + if (correlateStack) { + operatorLine = Traces.extractOperatorAssemblyInformation(Traces.callSiteSupplierFactory.get().get()); + } + else { + operatorLine = null; + } + + boolean generated = + category == null || category.isEmpty() || category.endsWith("."); + + category = generated && category == null ? "reactor." : category; + if (generated) { + if (source instanceof Mono) { + category += "Mono." + source.getClass() + .getSimpleName() + .replace("Mono", ""); + } + else if (source instanceof ParallelFlux) { + category += "Parallel." + source.getClass() + .getSimpleName() + .replace("Parallel", ""); + } + else { + category += "Flux." + source.getClass() + .getSimpleName() + .replace("Flux", ""); + } + category += "." + id; + } + + this.log = loggerSupplier.apply(category); + + this.level = level; + if (options == null || options.length == 0) { + this.options = ALL; + } + else { + int opts = 0; + for (SignalType option : options) { + if (option == SignalType.CANCEL) { + opts |= CANCEL; + } + else if (option == SignalType.CURRENT_CONTEXT) { + opts |= CONTEXT_PARENT; + } + else if (option == SignalType.ON_SUBSCRIBE) { + opts |= ON_SUBSCRIBE; + } + else if (option == SignalType.REQUEST) { + opts |= REQUEST; + } + else if (option == SignalType.ON_NEXT) { + opts |= ON_NEXT; + } + else if (option == SignalType.ON_ERROR) { + opts |= ON_ERROR; + } + else if (option == SignalType.ON_COMPLETE) { + opts |= ON_COMPLETE; + } + else if (option == SignalType.SUBSCRIBE) { + opts |= SUBSCRIBE; + } + else if (option == SignalType.AFTER_TERMINATE) { + opts |= AFTER_TERMINATE; + } + } + this.options = opts; + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return source; + + return null; + } + + /** + * Structured logging with level adaptation and operator ascii graph if required. + * + * @param signalType the type of signal being logged + * @param signalValue the value for the signal (use empty string if not required) + */ + void log(SignalType signalType, Object signalValue) { + String line = fuseable ? LOG_TEMPLATE_FUSEABLE : LOG_TEMPLATE; + if (operatorLine != null) { + line = line + " " + operatorLine; + } + if (level == Level.FINEST) { + log.trace(line, signalType, signalValue); + } + else if (level == Level.FINE) { + log.debug(line, signalType, signalValue); + } + else if (level == Level.INFO) { + log.info(line, signalType, signalValue); + } + else if (level == Level.WARNING) { + log.warn(line, signalType, signalValue); + } + else if (level == Level.SEVERE) { + log.error(line, signalType, signalValue); + } + } + + /** + * Structured logging with level adaptation and operator ascii graph if required + + * protection against loggers that detect objects like {@link Fuseable.QueueSubscription} + * as {@link java.util.Collection} and attempt to use their iterator for logging. + * + * @see #log + */ + void safeLog(SignalType signalType, Object signalValue) { + if (signalValue instanceof Fuseable.QueueSubscription) { + signalValue = String.valueOf(signalValue); + if (log.isDebugEnabled()) { + log.debug("A Fuseable Subscription has been passed to the logging framework, this is generally a sign of a misplaced log(), " + + "eg. 'window(2).log()' instead of 'window(2).flatMap(w -> w.log())'"); + } + } + try { + log(signalType, signalValue); + } + catch (UnsupportedOperationException uoe) { + log(signalType, String.valueOf(signalValue)); + if (log.isDebugEnabled()) { + log.debug("UnsupportedOperationException has been raised by the logging framework, does your log() placement make sense? " + + "eg. 'window(2).log()' instead of 'window(2).flatMap(w -> w.log())'", uoe); + } + } + } + + + static String subscriptionAsString(@Nullable Subscription s) { + if (s == null) { + return "null subscription"; + } + StringBuilder asString = new StringBuilder(); + if (s instanceof Fuseable.SynchronousSubscription) { + asString.append("[Synchronous Fuseable] "); + } + else if (s instanceof Fuseable.QueueSubscription) { + asString.append("[Fuseable] "); + } + + Class clazz = s.getClass(); + String name = clazz.getCanonicalName(); + if (name == null) { + name = clazz.getName(); + } + name = name.replaceFirst(clazz.getPackage() + .getName() + ".", ""); + asString.append(name); + + return asString.toString(); + } + + @Override + @Nullable + public Consumer onSubscribeCall() { + if ((options & ON_SUBSCRIBE) == ON_SUBSCRIBE && (level != Level.INFO || log.isInfoEnabled())) { + return s -> log(SignalType.ON_SUBSCRIBE, subscriptionAsString(s)); + } + return null; + } + + @Nullable + @Override + public Consumer onCurrentContextCall() { + if ((options & CONTEXT_PARENT) == CONTEXT_PARENT && ( + (level == Level.FINE && log.isDebugEnabled()) + || (level == Level.FINEST && log.isTraceEnabled()))) { + return c -> log(SignalType.CURRENT_CONTEXT, c); + } + return null; + } + + @Override + @Nullable + public Consumer onNextCall() { + if ((options & ON_NEXT) == ON_NEXT && (level != Level.INFO || log.isInfoEnabled())) { + return d -> safeLog(SignalType.ON_NEXT, d); + } + return null; + } + + @Override + @Nullable + public Consumer onErrorCall() { + boolean shouldLogAsDebug = level == Level.FINE && log.isDebugEnabled(); + boolean shouldLogAsTrace = level == Level.FINEST && log.isTraceEnabled(); + boolean shouldLogAsError = level != Level.FINE && level != Level.FINEST && log.isErrorEnabled(); + if ((options & ON_ERROR) == ON_ERROR && (shouldLogAsError || shouldLogAsDebug || + shouldLogAsTrace)) { + String line = fuseable ? LOG_TEMPLATE_FUSEABLE : LOG_TEMPLATE; + if (operatorLine != null) { + line = line + " " + operatorLine; + } + String s = line; + if (shouldLogAsTrace) { + return e -> { + log.trace(s, SignalType.ON_ERROR, e, source); + log.trace("", e); + }; + } + else if (shouldLogAsDebug) { + return e -> { + log.debug(s, SignalType.ON_ERROR, e, source); + log.debug("", e); + }; + } + else { //shouldLogAsError + return e -> { + log.error(s, SignalType.ON_ERROR, e, source); + log.error("", e); + }; + } + } + return null; + } + + @Override + @Nullable + public Runnable onCompleteCall() { + if ((options & ON_COMPLETE) == ON_COMPLETE && (level != Level.INFO || log.isInfoEnabled())) { + return () -> log(SignalType.ON_COMPLETE, ""); + } + return null; + } + + @Override + @Nullable + public Runnable onAfterTerminateCall() { + if ((options & AFTER_TERMINATE) == AFTER_TERMINATE && (level != Level.INFO || log.isInfoEnabled())) { + return () -> log(SignalType.AFTER_TERMINATE, ""); + } + return null; + } + + @Override + @Nullable + public LongConsumer onRequestCall() { + if ((options & REQUEST) == REQUEST && (level != Level.INFO || log.isInfoEnabled())) { + return n -> log(SignalType.REQUEST, + Long.MAX_VALUE == n ? "unbounded" : n); + } + return null; + } + + @Override + @Nullable + public Runnable onCancelCall() { + if ((options & CANCEL) == CANCEL && (level != Level.INFO || log.isInfoEnabled())) { + return () -> log(SignalType.CANCEL, ""); + } + return null; + } + + @Override + public String toString() { + return "/loggers/" + log.getName() + "/" + id; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SignalPeek.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SignalPeek.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SignalPeek.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016-2021 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.Consumer; +import java.util.function.LongConsumer; + +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * Peek into the lifecycle and sequence signals. + *

+ * The callbacks are all optional. + * + * @param the value type of the sequence + */ +interface SignalPeek extends Scannable { + + /** + * A consumer that will observe {@link Subscriber#onSubscribe(Subscription)} + * + * @return A consumer that will observe {@link Subscriber#onSubscribe(Subscription)} + */ + @Nullable + Consumer onSubscribeCall(); + + /** + * A consumer that will observe {@link Subscriber#onNext(Object)} + * + * @return A consumer that will observe {@link Subscriber#onNext(Object)} + */ + @Nullable + Consumer onNextCall(); + + /** + * A consumer that will observe {@link Subscriber#onError(Throwable)}} + * + * @return A consumer that will observe {@link Subscriber#onError(Throwable)} + */ + @Nullable + Consumer onErrorCall(); + + /** + * A task that will run on {@link Subscriber#onComplete()} + * + * @return A task that will run on {@link Subscriber#onComplete()} + */ + @Nullable + Runnable onCompleteCall(); + + /** + * A task will run after termination via {@link Subscriber#onComplete()} or {@link Subscriber#onError(Throwable)} + * + * @return A task will run after termination via {@link Subscriber#onComplete()} or {@link Subscriber#onError(Throwable)} + */ + @Nullable + Runnable onAfterTerminateCall(); + + /** + * A consumer of long that will observe {@link Subscription#request(long)}} + * + * @return A consumer of long that will observe {@link Subscription#request(long)}} + */ + @Nullable + LongConsumer onRequestCall(); + + /** + * A task that will run on {@link Subscription#cancel()} + * + * @return A task that will run on {@link Subscription#cancel()} + */ + @Nullable + Runnable onCancelCall(); + + /** + * A task that will run after (finally) {@link Subscriber#onNext(Object)} + * @return A task that will run after (finally) {@link Subscriber#onNext(Object)} + */ + @Nullable + default Consumer onAfterNextCall(){ + return null; + } + + /** + * A task that will run on {@link Context} read from downstream to upstream + * @return A task that will run on {@link Context} propagation from upstream to downstream + */ + @Nullable + default Consumer onCurrentContextCall(){ + return null; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SignalType.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SignalType.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SignalType.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016-2021 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; + + +/** + * Reactive Stream signal types + */ +public enum SignalType { + + /** + * A signal when the subscription is triggered + */ + SUBSCRIBE, + /** + * A signal when a request is made through the subscription + */ + REQUEST, + /** + * A signal when the subscription is cancelled + */ + CANCEL, + /** + * A signal when an operator receives a subscription + */ + ON_SUBSCRIBE, + /** + * A signal when an operator receives an emitted value + */ + ON_NEXT, + /** + * A signal when an operator receives an error + */ + ON_ERROR, + /** + * A signal when an operator completes + */ + ON_COMPLETE, + /** + * A signal when an operator completes + */ + AFTER_TERMINATE, + /** + * A context read signal + */ + CURRENT_CONTEXT, + /** + * A context update signal + */ + ON_CONTEXT; + + @Override + public String toString() { + switch (this) { + case ON_SUBSCRIBE: + return "onSubscribe"; + case ON_NEXT: + return "onNext"; + case ON_ERROR: + return "onError"; + case ON_COMPLETE: + return "onComplete"; + case REQUEST: + return "request"; + case CANCEL: + return "cancel"; + case CURRENT_CONTEXT: + return "currentContext"; + case ON_CONTEXT: + return "onContextUpdate"; + case AFTER_TERMINATE: + return "afterTerminate"; + default: + return "subscribe"; + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkEmptyMulticast.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkEmptyMulticast.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkEmptyMulticast.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2020-2021 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.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import reactor.core.CoreSubscriber; +import reactor.core.Scannable; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +//intentionally not final +class SinkEmptyMulticast extends Mono implements InternalEmptySink { + + volatile Inner[] subscribers; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater SUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater(SinkEmptyMulticast.class, Inner[].class, "subscribers"); + + @SuppressWarnings("rawtypes") + static final Inner[] EMPTY = new Inner[0]; + + @SuppressWarnings("rawtypes") + static final Inner[] TERMINATED = new Inner[0]; + + @Nullable + Throwable error; + + SinkEmptyMulticast() { + SUBSCRIBERS.lazySet(this, EMPTY); + } + + @Override + public int currentSubscriberCount() { + return subscribers.length; + } + + @Override + public Mono asMono() { + return this; + } + + @Override + public EmitResult tryEmitEmpty() { + Inner[] array = SUBSCRIBERS.getAndSet(this, TERMINATED); + + if (array == TERMINATED) { + return Sinks.EmitResult.FAIL_TERMINATED; + } + + for (Inner as : array) { + as.complete(); + } + return EmitResult.OK; + } + + @Override + @SuppressWarnings("unchecked") + public EmitResult tryEmitError(Throwable cause) { + Objects.requireNonNull(cause, "onError cannot be null"); + + Inner[] prevSubscribers = SUBSCRIBERS.getAndSet(this, TERMINATED); + if (prevSubscribers == TERMINATED) { + return EmitResult.FAIL_TERMINATED; + } + + error = cause; + + 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.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + boolean add(Inner ps) { + for (; ; ) { + Inner[] a = subscribers; + + if (a == TERMINATED) { + return false; + } + + 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; + } + } + } + + @SuppressWarnings("unchecked") + void remove(Inner ps) { + for (; ; ) { + Inner[] a = subscribers; + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == ps) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + + Inner[] b; + + if (n == 1) { + b = EMPTY; + } + else { + b = new Inner[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)) { + return; + } + } + } + + //redefined in SinkOneMulticast + @Override + public void subscribe(final CoreSubscriber actual) { + Inner as = new VoidInner<>(actual, this); + actual.onSubscribe(as); + if (add(as)) { + if (as.isCancelled()) { + remove(as); + } + } + else { + Throwable ex = error; + if (ex != null) { + actual.onError(ex); + } + else { + as.complete(); + } + } + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + static interface Inner extends InnerProducer { + //API must be compatible with Operators.MonoInnerProducerBase + + void error(Throwable t); + void complete(T value); + void complete(); + boolean isCancelled(); + } + + //VoidInner is optimized for not storing request / value + final static class VoidInner extends AtomicBoolean implements Inner { + + final SinkEmptyMulticast parent; + final CoreSubscriber actual; + + VoidInner(CoreSubscriber actual, SinkEmptyMulticast parent) { + this.actual = actual; + this.parent = parent; + } + + @Override + public void cancel() { + if (getAndSet(true)) { + return; + } + + parent.remove(this); + } + + @Override + public boolean isCancelled() { + return get(); + } + + @Override + public void request(long l) { + Operators.validate(l); + } + + @Override + public void complete(T value) { + //NO-OP + } + + @Override + public void complete() { + if (get()) { + return; + } + actual.onComplete(); + } + + @Override + public void error(Throwable t) { + if (get()) { + Operators.onOperatorError(t, actual.currentContext()); + return; + } + actual.onError(t); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return parent; + } + if (key == Attr.CANCELLED) { + return get(); + } + if (key == Attr.RUN_STYLE) { + return Attr.RunStyle.SYNC; + } + return Inner.super.scanUnsafe(key); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkEmptySerialized.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkEmptySerialized.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkEmptySerialized.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020-2021 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.Scannable; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.core.publisher.Sinks.Empty; +import reactor.util.context.Context; + +import java.util.Objects; +import java.util.stream.Stream; + +class SinkEmptySerialized extends SinksSpecs.AbstractSerializedSink + implements InternalEmptySink, ContextHolder { + + final Empty sink; + final ContextHolder contextHolder; + + SinkEmptySerialized(Empty sink, ContextHolder contextHolder) { + this.sink = sink; + this.contextHolder = contextHolder; + } + + @Override + public final EmitResult tryEmitEmpty() { + Thread currentThread = Thread.currentThread(); + if (!tryAcquire(currentThread)) { + return EmitResult.FAIL_NON_SERIALIZED; + } + + try { + return sink.tryEmitEmpty(); + } + finally { + if (WIP.decrementAndGet(this) == 0) { + LOCKED_AT.compareAndSet(this, currentThread, null); + } + } + } + + @Override + public final EmitResult tryEmitError(Throwable t) { + Objects.requireNonNull(t, "t is null in sink.error(t)"); + + Thread currentThread = Thread.currentThread(); + if (!tryAcquire(currentThread)) { + return EmitResult.FAIL_NON_SERIALIZED; + } + + try { + return sink.tryEmitError(t); + } + finally { + if (WIP.decrementAndGet(this) == 0) { + LOCKED_AT.compareAndSet(this, currentThread, null); + } + } + } + + @Override + public int currentSubscriberCount() { + return sink.currentSubscriberCount(); + } + + @Override + public Mono asMono() { + return sink.asMono(); + } + + @Override + public Stream inners() { + return sink.inners(); + } + + @Override + public Object scanUnsafe(Attr key) { + return sink.scanUnsafe(key); + } + + @Override + public Context currentContext() { + return contextHolder.currentContext(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkManyBestEffort.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkManyBestEffort.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkManyBestEffort.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2020-2021 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.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.stream.Stream; + +import reactor.core.CoreSubscriber; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +/** + * @author Simon Baslé + */ +final class SinkManyBestEffort extends Flux + implements InternalManySink, Scannable, + DirectInnerContainer { + + static final DirectInner[] EMPTY = new DirectInner[0]; + static final DirectInner[] TERMINATED = new DirectInner[0]; + + static final SinkManyBestEffort createBestEffort() { + return new SinkManyBestEffort<>(false); + } + + static final SinkManyBestEffort createAllOrNothing() { + return new SinkManyBestEffort<>(true); + } + + final boolean allOrNothing; + + /** + * Stores the error that terminated this sink, for immediate replay to late subscribers + */ + Throwable error; + + volatile DirectInner[] subscribers; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdaterSUBSCRIBERS = + AtomicReferenceFieldUpdater.newUpdater(SinkManyBestEffort.class, DirectInner[].class, "subscribers"); + + SinkManyBestEffort(boolean allOrNothing) { + this.allOrNothing = allOrNothing; + SUBSCRIBERS.lazySet(this, EMPTY); + } + + public Context currentContext() { + return Operators.multiSubscribersContext(subscribers); + } + + @Override + public Stream inners() { + return Stream.of(subscribers); + } + + @Nullable + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED) return subscribers == TERMINATED; + if (key == Attr.ERROR) return error; + + return null; + } + + @Override + public EmitResult tryEmitNext(T t) { + Objects.requireNonNull(t, "tryEmitNext(null) is forbidden"); + + DirectInner[] subs = subscribers; + + if (subs == EMPTY) { + return EmitResult.FAIL_ZERO_SUBSCRIBER; + } + if (subs == TERMINATED) { + return EmitResult.FAIL_TERMINATED; + } + + int expectedEmitted = subs.length; + int cancelledCount = 0; + if (allOrNothing) { + long commonRequest = Long.MAX_VALUE; + for (DirectInner sub : subs) { + long subRequest = sub.requested; + if (sub.isCancelled()) { + cancelledCount++; + continue; + } + if (subRequest < commonRequest) { + commonRequest = subRequest; + } + } + if (commonRequest == 0) { + return EmitResult.FAIL_OVERFLOW; + } + if (cancelledCount == expectedEmitted) { + return EmitResult.FAIL_ZERO_SUBSCRIBER; + } + } + + int emittedCount = 0; + cancelledCount = 0; + for (DirectInner sub : subs) { + if (sub.isCancelled()) { + cancelledCount++; + continue; + } + if (sub.tryEmitNext(t)) { + emittedCount++; + } + else if (sub.isCancelled()) { + cancelledCount++; + } + } + if (cancelledCount == expectedEmitted) { + return EmitResult.FAIL_ZERO_SUBSCRIBER; + } + else if (cancelledCount + emittedCount == expectedEmitted) { + return EmitResult.OK; + } + else if (emittedCount > 0 && !allOrNothing) { + return EmitResult.OK; + } + else { + return EmitResult.FAIL_OVERFLOW; + } + } + + @Override + public EmitResult tryEmitComplete() { + @SuppressWarnings("unchecked") + DirectInner[] subs = SUBSCRIBERS.getAndSet(this, TERMINATED); + + if (subs == TERMINATED) { + return EmitResult.FAIL_TERMINATED; + } + + for (DirectInner s : subs) { + s.emitComplete(); + } + return EmitResult.OK; + } + + @Override + public EmitResult tryEmitError(Throwable error) { + Objects.requireNonNull(error, "tryEmitError(null) is forbidden"); + @SuppressWarnings("unchecked") + DirectInner[] subs = SUBSCRIBERS.getAndSet(this, TERMINATED); + + if (subs == TERMINATED) { + return EmitResult.FAIL_TERMINATED; + } + + this.error = error; + + for (DirectInner s : subs) { + s.emitError(error); + } + return EmitResult.OK; + } + + @Override + public int currentSubscriberCount() { + return subscribers.length; + } + + @Override + public Flux asFlux() { + return this; + } + + @Override + public void subscribe(CoreSubscriber actual) { + Objects.requireNonNull(actual, "subscribe(null) is forbidden"); + + DirectInner p = new DirectInner<>(actual, this); + actual.onSubscribe(p); + + if (p.isCancelled()) { + return; + } + + if (add(p)) { + if (p.isCancelled()) { + remove(p); + } + } + else { + Throwable e = error; + if (e != null) { + actual.onError(e); + } + else { + actual.onComplete(); + } + } + } + + @Override + public boolean add(DirectInner s) { + DirectInner[] a = subscribers; + if (a == TERMINATED) { + return false; + } + + for (;;) { + a = subscribers; + if (a == TERMINATED) { + return false; + } + int len = a.length; + + @SuppressWarnings("unchecked") + DirectInner[] b = new DirectInner[len + 1]; + System.arraycopy(a, 0, b, 0, len); + b[len] = s; + + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + @Override + public void remove(DirectInner s) { + DirectInner[] a = subscribers; + if (a == TERMINATED || a == EMPTY) { + return; + } + + for (;;) { + a = subscribers; + if (a == TERMINATED || a == EMPTY) { + return; + } + int len = a.length; + + int j = -1; + + for (int i = 0; i < len; i++) { + if (a[i] == s) { + j = i; + break; + } + } + if (j < 0) { + return; + } + + DirectInner[] b; + if (len == 1) { + b = EMPTY; + } else { + b = new DirectInner[len - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, len - j - 1); + } + + if (SUBSCRIBERS.compareAndSet(this, a, b)) { + return; + } + } + } + + static class DirectInner extends AtomicBoolean implements InnerProducer { + + final CoreSubscriber actual; + final DirectInnerContainer parent; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = AtomicLongFieldUpdater.newUpdater( + DirectInner.class, "requested"); + + DirectInner(CoreSubscriber actual, DirectInnerContainer parent) { + this.actual = actual; + this.parent = parent; + } + + @Override + public void request(long n) { + if (Operators.validate(n)) { + Operators.addCap(REQUESTED, this, n); + } + } + + @Override + public void cancel() { + if (compareAndSet(false, true)) { + parent.remove(this); + } + } + + boolean isCancelled() { + return get(); + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return parent; + if (key == Attr.CANCELLED) return isCancelled(); + + return InnerProducer.super.scanUnsafe(key); + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + /** + * Try to emit if the downstream is not cancelled and has some demand. + * @param value the value to emit + * @return true if enough demand and not cancelled, false otherwise + */ + boolean tryEmitNext(T value) { + if (requested != 0L) { + if (isCancelled()) { + return false; + } + actual.onNext(value); + if (requested != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + return true; + } + return false; + } + + /** + * Emit a value to the downstream, unless it doesn't have sufficient demand. + * In that case, the downstream is terminated with an {@link Exceptions#failWithOverflow()}. + * + * @param value the value to emit + */ + void directEmitNext(T value) { + if (requested != 0L) { + actual.onNext(value); + if (requested != Long.MAX_VALUE) { + REQUESTED.decrementAndGet(this); + } + return; + } + parent.remove(this); + actual.onError(Exceptions.failWithOverflow("Can't deliver value due to lack of requests")); + } + + void emitError(Throwable e) { + if (isCancelled()) { + return; + } + actual.onError(e); + } + + void emitComplete() { + if (isCancelled()) { + return; + } + actual.onComplete(); + } + } + +} + Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkManySerialized.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkManySerialized.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkManySerialized.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2020-2021 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.Scannable; +import reactor.util.annotation.Nullable; +import reactor.util.context.Context; + +import java.util.Objects; +import java.util.stream.Stream; + +final class SinkManySerialized extends SinksSpecs.AbstractSerializedSink + implements InternalManySink, Scannable { + + final Sinks.Many sink; + final ContextHolder contextHolder; + + SinkManySerialized(Sinks.Many sink, ContextHolder contextHolder) { + this.sink = sink; + this.contextHolder = contextHolder; + } + + @Override + public int currentSubscriberCount() { + return sink.currentSubscriberCount(); + } + + @Override + public Flux asFlux() { + return sink.asFlux(); + } + + @Override + public Context currentContext() { + return contextHolder.currentContext(); + } + + public boolean isCancelled() { + return Scannable.from(sink).scanOrDefault(Attr.CANCELLED, false); + } + + @Override + public final Sinks.EmitResult tryEmitComplete() { + Thread currentThread = Thread.currentThread(); + if (!tryAcquire(currentThread)) { + return Sinks.EmitResult.FAIL_NON_SERIALIZED; + } + + try { + return sink.tryEmitComplete(); + } finally { + if (WIP.decrementAndGet(this) == 0) { + LOCKED_AT.compareAndSet(this, currentThread, null); + } + } + } + + @Override + public final Sinks.EmitResult tryEmitError(Throwable t) { + Objects.requireNonNull(t, "t is null in sink.error(t)"); + + Thread currentThread = Thread.currentThread(); + if (!tryAcquire(currentThread)) { + return Sinks.EmitResult.FAIL_NON_SERIALIZED; + } + + try { + return sink.tryEmitError(t); + } finally { + if (WIP.decrementAndGet(this) == 0) { + LOCKED_AT.compareAndSet(this, currentThread, null); + } + } + } + + @Override + public final Sinks.EmitResult tryEmitNext(T t) { + Objects.requireNonNull(t, "t is null in sink.next(t)"); + + Thread currentThread = Thread.currentThread(); + if (!tryAcquire(currentThread)) { + return Sinks.EmitResult.FAIL_NON_SERIALIZED; + } + + try { + return sink.tryEmitNext(t); + } finally { + if (WIP.decrementAndGet(this) == 0) { + LOCKED_AT.compareAndSet(this, currentThread, null); + } + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + return sink.scanUnsafe(key); + } + + @Override + public Stream inners() { + return Scannable.from(sink).inners(); + } + + @Override + public String toString() { + return sink.toString(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SinkOneMulticast.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkOneMulticast.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkOneMulticast.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.CoreSubscriber; +import reactor.core.publisher.Sinks.EmitResult; +import reactor.util.annotation.Nullable; + +final class SinkOneMulticast extends SinkEmptyMulticast implements InternalOneSink { + + @Nullable + O value; + + @Override + public EmitResult tryEmitEmpty() { + return tryEmitValue(null); + } + + @Override + @SuppressWarnings("unchecked") + public EmitResult tryEmitError(Throwable cause) { + Objects.requireNonNull(cause, "onError cannot be null"); + + Inner[] prevSubscribers = SUBSCRIBERS.getAndSet(this, TERMINATED); + if (prevSubscribers == TERMINATED) { + return EmitResult.FAIL_TERMINATED; + } + + error = cause; + value = null; + + for (Inner as : prevSubscribers) { + as.error(cause); + } + 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(); + } + } + else { + for (Inner as : array) { + 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.RUN_STYLE) return Attr.RunStyle.SYNC; + + return null; + } + + @Override + public void subscribe(final CoreSubscriber actual) { + NextInner as = new NextInner<>(actual, this); + actual.onSubscribe(as); + if (add(as)) { + if (as.isCancelled()) { + remove(as); + } + } + else { + Throwable ex = error; + if (ex != null) { + actual.onError(ex); + } + else { + O v = value; + if (v != null) { + as.complete(v); + } + else { + as.complete(); + } + } + } + } + + @Nullable + @Override + public O block(Duration timeout) { + if (timeout.isNegative()) { + return super.block(Duration.ZERO); + } + return super.block(timeout); + } + + final static class NextInner extends Operators.MonoInnerProducerBase implements Inner { + + final SinkOneMulticast parent; + + NextInner(CoreSubscriber actual, SinkOneMulticast parent) { + super(actual); + this.parent = parent; + } + + @Override + public void error(Throwable t) { + if (!isCancelled()) { + actual().onError(t); + } + } + + @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/SinkOneSerialized.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinkOneSerialized.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinkOneSerialized.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2021 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.publisher.Sinks.One; + +public class SinkOneSerialized extends SinkEmptySerialized implements InternalOneSink, ContextHolder { + + final One sinkOne; + + public SinkOneSerialized(One sinkOne, ContextHolder contextHolder) { + super(sinkOne, contextHolder); + this.sinkOne = sinkOne; + } + + @Override + public Sinks.EmitResult tryEmitValue(T t) { + Thread currentThread = Thread.currentThread(); + if (!tryAcquire(currentThread)) { + return Sinks.EmitResult.FAIL_NON_SERIALIZED; + } + + try { + return sinkOne.tryEmitValue(t); + } + finally { + if (WIP.decrementAndGet(this) == 0) { + LOCKED_AT.compareAndSet(this, currentThread, null); + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/Sinks.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/Sinks.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/Sinks.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,1092 @@ +/* + * Copyright (c) 2020-2021 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.Queue; + +import org.reactivestreams.Subscriber; + +import reactor.core.Disposable; +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; + +/** + * Sinks are constructs through which Reactive Streams signals can be programmatically pushed, with {@link Flux} or {@link Mono} + * 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 + * {@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 + * of detecting concurrent access from the sink itself). + * + * @author Simon Baslé + * @author Stephane Maldini + */ +public final class Sinks { + + private Sinks() { + } + + /** + * A {@link Sinks.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. + * + * @return a new {@link Sinks.Empty} + * @see RootSpec#empty() + */ + public static Sinks.Empty empty() { + return SinksSpecs.DEFAULT_ROOT_SPEC.empty(); + } + + /** + * A {@link Sinks.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 + * 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} + * @see RootSpec#one() + */ + public static Sinks.One one() { + return SinksSpecs.DEFAULT_ROOT_SPEC.one(); + } + + /** + * Help building {@link Sinks.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 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, + * 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 + * don't care to detect concurrent access anymore. + * + * @return {@link RootSpec} + */ + public static RootSpec unsafe() { + return SinksSpecs.UNSAFE_ROOT_SPEC; + } + + /** + * Represents the immediate result of an emit attempt (eg. in {@link Sinks.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 + * if an in-flight cancellation is happening. This is due to the async nature of these actions: producer emits while + * consumer can interrupt independently. + */ + public enum EmitResult { + /** + * Has successfully emitted the signal + */ + OK, + /** + * Has failed to emit the signal because the sink was previously terminated successfully or with an error + */ + FAIL_TERMINATED, + /** + * Has failed to emit the signal because the sink does not have buffering capacity left + */ + FAIL_OVERFLOW, + /** + * Has failed to emit the signal because the sink was previously interrupted by its consumer + */ + FAIL_CANCELLED, + /** + * Has failed to emit the signal because the access was not serialized + */ + FAIL_NON_SERIALIZED, + /** + * Has failed to emit the signal because the sink has never been subscribed to has no capacity + * to buffer the signal. + */ + FAIL_ZERO_SUBSCRIBER; + + /** + * Represents a successful emission of a signal. + *

+ * This is more future-proof than checking for equality with {@code OK} since + * new OK-like codes could be introduced later. + */ + public boolean isSuccess() { + return this == OK; + } + + /** + * Represents a failure to emit a signal. + */ + public boolean isFailure() { + return this != OK; + } + + /** + * Easily convert from an {@link EmitResult} to throwing an exception on {@link #isFailure() failure cases}. + * This is useful if throwing is the most relevant way of dealing with a failed emission attempt. + * Note however that generally Reactor code doesn't favor throwing exceptions but rather propagating + * them through onError signals. + * See also {@link #orThrowWithCause(Throwable)} in case of an {@link One#tryEmitError(Throwable) emitError} + * failure for which you want to attach the originally pushed {@link Exception}. + * + * @see #orThrowWithCause(Throwable) + */ + public void orThrow() { + if (this == OK) return; + + throw new EmissionException(this); + } + + /** + * Easily convert from an {@link EmitResult} to throwing an exception on {@link #isFailure() failure cases}. + * This is useful if throwing is the most relevant way of dealing with failed {@link One#tryEmitError(Throwable) tryEmitError} + * attempt, in which case you probably wants to propagate the originally pushed {@link Exception}. + * Note however that generally Reactor code doesn't favor throwing exceptions but rather propagating + * them through onError signals. + * + * @see #orThrow() + */ + public void orThrowWithCause(Throwable cause) { + if (this == OK) return; + + throw new EmissionException(cause, this); + } + } + + /** + * An exception representing a {@link EmitResult#isFailure() failed} {@link EmitResult}. + * The exact type of failure can be found via {@link #getReason()}. + */ + public static final class EmissionException extends IllegalStateException { + + final EmitResult reason; + + public EmissionException(EmitResult reason) { + this(reason, "Sink emission failed with " + reason); + } + + public EmissionException(Throwable cause, EmitResult reason) { + super("Sink emission failed with " + reason, cause); + this.reason = reason; + } + + public EmissionException(EmitResult reason, String message) { + super(message); + this.reason = reason; + } + + /** + * Get the failure {@link EmitResult} code that is represented by this exception. + * + * @return the {@link EmitResult} + */ + public EmitResult getReason() { + return this.reason; + } + } + + /** + * A handler supporting the emit API (eg. {@link Many#emitNext(Object, Sinks.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 + * like parking the current thread for longer retry loops. They don't, however, influence the + * exact action taken by the emit API implementations when the handler doesn't allow + * the retry to occur. + * + * @implNote It is expected that the handler may perform side effects (e.g. busy looping) + * and should not be considered a plain {@link java.util.function.Predicate}. + */ + public interface EmitFailureHandler { + + /** + * A pre-made handler that will not instruct to retry any failure + * and trigger the failure handling immediately. + */ + EmitFailureHandler FAIL_FAST = (signalType, emission) -> false; + + /** + * 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. + * + * @param signalType the signal that triggered the emission. Can be either {@link SignalType#ON_NEXT}, {@link SignalType#ON_ERROR} or {@link SignalType#ON_COMPLETE}. + * @param emitResult the result of the emission (a failure) + * @return {@code true} if the operation should be retried, {@code false} otherwise. + */ + boolean onEmitFailure(SignalType signalType, EmitResult emitResult); + } + + /** + * Provides a choice of {@link Sinks.One}/{@link Sinks.Empty} factories and + * {@link Sinks.ManySpec further specs} for {@link Sinks.Many}. + */ + public interface RootSpec { + + /** + * A {@link Sinks.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. + */ + Sinks.Empty empty(); + + /** + * A {@link Sinks.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 + * {@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(); + + /** + * Help building {@link Sinks.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(); + } + + /** + * Provides {@link Sinks.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} + * + * @return {@link UnicastSpec} + */ + UnicastSpec unicast(); + + /** + * Help building {@link Sinks.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 + * and replay all or an arbitrary number of elements. + * + * @return {@link MulticastReplaySpec} + */ + MulticastReplaySpec replay(); + } + + /** + * Provides unicast: 1 sink, 1 {@link Subscriber} + */ + public interface UnicastSpec { + + /** + * A {@link Sinks.Many} with the following characteristics: + *

    + *
  • Unicast: contrary to most other {@link Sinks.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(); + + /** + * A {@link Sinks.Many} with the following characteristics: + *
    + *
  • Unicast: contrary to most other {@link Sinks.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}: depending on the queue, all elements pushed to this sink are remembered and will + * be replayed once the {@link Subscriber} subscribes.
  • + *
+ * + * @param queue an arbitrary queue to use that must at least support Single Producer / Single Consumer semantics + */ + Sinks.Many onBackpressureBuffer(Queue queue); + + /** + * A {@link Sinks.Many} with the following characteristics: + *
    + *
  • Unicast: contrary to most other {@link Sinks.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}: depending on the queue, all elements pushed to this sink are remembered and will + * be replayed once the {@link Subscriber} subscribes.
  • + *
+ * + * @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); + + /** + * A {@link Sinks.Many} with the following characteristics: + *
    + *
  • Unicast: contrary to most other {@link Sinks.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(); + } + + /** + * Provides multicast : 1 sink, N {@link Subscriber} + */ + public interface MulticastSpec { + + /** + * A {@link Sinks.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} + * 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.
  • + *
+ *

+ * + */ + Sinks.Many onBackpressureBuffer(); + + /** + * A {@link Sinks.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} + * 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 + */ + Sinks.Many onBackpressureBuffer(int bufferSize); + + /** + * A {@link Sinks.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} + * 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 + */ + Sinks.Many onBackpressureBuffer(int bufferSize, boolean autoCancel); + + /** + A {@link Sinks.Many} with the following characteristics: + *

    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: fail fast on {@link Many#tryEmitNext(Object) tryEmitNext}.
  • + *
  • Backpressure : notify the caller with {@link EmitResult#FAIL_OVERFLOW} if any of the subscribers + * cannot process an element, failing fast and backing off from emitting the element at all (all or nothing). + * From the perspective of subscribers, data is dropped and never seen but they are not terminated. + *
  • + *
  • Replaying: No replay of elements. Only forwards to a {@link Subscriber} the elements that + * have been pushed to the sink AFTER this subscriber was subscribed, provided all of the subscribers + * have demand.
  • + *
+ *

+ * + * + * @param the type of elements to emit + * @return a multicast {@link Sinks.Many} that "drops" in case any subscriber is too slow + */ + Sinks.Many directAllOrNothing(); + + /** + A {@link Sinks.Many} with the following characteristics: + *

    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: fail fast on {@link Many#tryEmitNext(Object) tryEmitNext}.
  • + *
  • Backpressure : notify the caller with {@link EmitResult#FAIL_OVERFLOW} if none + * of the subscribers can process an element. Otherwise, it ignores slow subscribers and emits the + * element to fast ones as a best effort. From the perspective of slow subscribers, data is dropped + * and never seen, but they are not terminated. + *
  • + *
  • Replaying: No replay of elements. Only forwards to a {@link Subscriber} the elements that + * have been pushed to the sink AFTER this subscriber was subscribed.
  • + *
+ *

+ * + * + * @param the type of elements to emit + * @return a multicast {@link Sinks.Many} that "drops" in case of no demand from any subscriber + */ + Sinks.Many directBestEffort(); + } + + /** + * Provides multicast with history/replay capacity : 1 sink, N {@link Subscriber} + */ + public interface MulticastReplaySpec { + /** + * A {@link Sinks.Many} with the following characteristics: + *

    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: all elements pushed to this sink are remembered, + * even when there is no subscriber.
  • + *
  • Backpressure : this sink honors downstream demand of individual subscribers.
  • + *
  • Replaying: all elements pushed to this sink are replayed to new subscribers.
  • + *
+ */ + Sinks.Many all(); + + /** + * A {@link Sinks.Many} with the following characteristics: + *
    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: all elements pushed to this sink are remembered, + * even when there is no subscriber.
  • + *
  • Backpressure : this sink honors downstream demand of individual subscribers.
  • + *
  • Replaying: all elements pushed to this sink are replayed to new subscribers.
  • + *
+ * @param batchSize the underlying buffer will optimize storage by linked arrays of given size + */ + Sinks.Many all(int batchSize); + + /** + * A {@link Sinks.Many} with the following characteristics: + *
    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: the latest element pushed to this sink are remembered, + * even when there is no subscriber. Older elements are discarded
  • + *
  • Backpressure : this sink honors downstream demand of individual subscribers.
  • + *
  • Replaying: the latest element pushed to this sink is replayed to new subscribers.
  • + *
+ */ + Sinks.Many latest(); + + /** + * A {@link Sinks.Many} with the following characteristics: + *
    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: the latest element pushed to this sink are remembered, + * even when there is no subscriber.
  • + *
  • Backpressure : this sink honors downstream demand of individual subscribers.
  • + *
  • Replaying: the latest element pushed to this sink is replayed to new subscribers. If none the default value is replayed
  • + *
+ * + * @param value default value if there is no latest element to replay + */ + Sinks.Many latestOrDefault(T value); + + /** + * A {@link Sinks.Many} with the following characteristics: + *
    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: up to {@code historySize} elements pushed to this sink are remembered, + * even when there is no subscriber. Older elements are discarded
  • + *
  • Backpressure : this sink honors downstream demand of individual subscribers.
  • + *
  • Replaying: up to {@code historySize} elements pushed to this sink are replayed to new subscribers. + * Older elements are discarded.
  • + *
+ *

+ * Note that though historySize of zero is forbidden, the desired equivalent effect can usually be achieved + * with the {@link Duration} based variant: {@link #limit(Duration) limit(Duration.ZERO)}. + * + * @param historySize maximum number of elements able to replayed, strictly positive + */ + Sinks.Many limit(int historySize); + + /** + * A {@link Sinks.Many} with the following characteristics: + *

    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: all elements pushed to this sink are remembered until their {@code maxAge} is reached, + * even when there is no subscriber. Older elements are discarded
  • + *
  • Backpressure : this sink honors downstream demand of individual subscribers.
  • + *
  • Replaying: up to {@code historySize} elements pushed to this sink are replayed to new subscribers. + * Older elements are discarded.
  • + *
+ * + * @param maxAge maximum retention time for elements to be retained + */ + Sinks.Many limit(Duration maxAge); + + /** + * A {@link Sinks.Many} with the following characteristics: + *
    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: all elements pushed to this sink are remembered until their {@code maxAge} is reached, + * even when there is no subscriber. Older elements are discarded
  • + *
  • Backpressure : this sink honors downstream demand of individual subscribers.
  • + *
  • Replaying: up to {@code historySize} elements pushed to this sink are replayed to new subscribers. + * Older elements are discarded.
  • + *
+ * Note: Age is checked when a signal occurs, not using a background task. + * + * @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); + + /** + * A {@link Sinks.Many} with the following characteristics: + *
    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: up to {@code historySize} elements pushed to this sink are remembered, + * until their {@code maxAge} is reached, even when there is no subscriber. Older elements are discarded
  • + *
  • Backpressure : this sink honors downstream demand of individual subscribers.
  • + *
  • Replaying: up to {@code historySize} elements pushed to this sink are replayed to new subscribers. + * Older elements are discarded.
  • + *
+ * Note: Age is checked when a signal occurs, not using a background task. + *

+ * Note that though historySize of zero is forbidden, the desired equivalent effect can usually be achieved + * by setting the {@code maxAge} to {@link Duration#ZERO}. + * + * @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); + + /** + * A {@link Sinks.Many} with the following characteristics: + *

    + *
  • Multicast
  • + *
  • Without {@link Subscriber}: up to {@code historySize} elements pushed to this sink are remembered, + * until their {@code maxAge} is reached, even when there is no subscriber. Older elements are discarded.
  • + *
  • Backpressure : this sink honors downstream demand of individual subscribers.
  • + *
  • Replaying: up to {@code historySize} elements pushed to this sink are replayed to new subscribers. + * Older elements are discarded.
  • + *
+ * Note: Age is checked when a signal occurs, not using a background task. + *

+ * Note that though historySize of zero is forbidden, the desired equivalent effect can usually be achieved + * by setting the {@code maxAge} to {@link Duration#ZERO}. + * + * @param historySize maximum number of elements able to replayed, strictly positive + * @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); + } + + /** + * A base interface for standalone {@link Sinks} with {@link Flux} semantics. + *

+ * The sink can be exposed to consuming code as a {@link Flux} via its {@link #asFlux()} view. + * + * @author Simon Baslé + * @author Stephane Maldini + */ + public interface Many extends Scannable { + + /** + * Try emitting a non-null element, generating an {@link Subscriber#onNext(Object) onNext} signal. + * The result of the attempt is represented as an {@link EmitResult}, which possibly indicates error cases. + *

+ * See the list of failure {@link EmitResult} in {@link #emitNext(Object, EmitFailureHandler)} javadoc for an + * example of how each of these can be dealt with, to decide if the emit API would be a good enough fit instead. + *

+ * Might throw an unchecked exception as a last resort (eg. in case of a fatal error downstream which cannot + * be propagated to any asynchronous handler, a bubbling exception, ...). + * + * @param t the value to emit, not null + * @return an {@link EmitResult}, which should be checked to distinguish different possible failures + * @see Subscriber#onNext(Object) + */ + EmitResult tryEmitNext(T t); + + /** + * Try to terminate the sequence successfully, generating an {@link Subscriber#onComplete() onComplete} + * signal. The result of the attempt is represented as an {@link EmitResult}, which possibly indicates error cases. + *

+ * See the list of failure {@link EmitResult} in {@link #emitComplete(EmitFailureHandler)} javadoc for an + * 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 Subscriber#onComplete() + */ + EmitResult tryEmitComplete(); + + /** + * Try to fail the sequence, generating an {@link Subscriber#onError(Throwable) onError} + * signal. The result of the attempt is represented as an {@link EmitResult}, which possibly indicates error cases. + *

+ * See the list of failure {@link EmitResult} in {@link #emitError(Throwable, EmitFailureHandler)} javadoc for an + * example of how each of these can be dealt with, to decide if the emit API would be a good enough fit instead. + * + * @param error the exception to signal, not null + * @return an {@link EmitResult}, which should be checked to distinguish different possible failures + * @see Subscriber#onError(Throwable) + */ + EmitResult tryEmitError(Throwable error); + + /** + * A simplified attempt at emitting a non-null element via the {@link #tryEmitNext(Object)} API, generating an + * {@link Subscriber#onNext(Object) onNext} signal. + * If the result of the attempt is not a {@link EmitResult#isSuccess() success}, implementations SHOULD retry the + * {@link #tryEmitNext(Object)} call IF the provided {@link EmitFailureHandler} returns {@code true}. + * Otherwise, failures are dealt with in a predefined way that might depend on the actual sink implementation + * (see below for the vanilla reactor-core behavior). + *

+ * Generally, {@link #tryEmitNext(Object)} is preferable since it allows a custom handling + * of error cases, although this implies checking the returned {@link EmitResult} and correctly + * acting on it. This API is intended as a good default for convenience. + *

+ * When the {@link EmitResult} is not a success, vanilla reactor-core operators have the following behavior: + *

    + *
  • + * {@link EmitResult#FAIL_ZERO_SUBSCRIBER}: no particular handling. should ideally discard the value but at that + * point there's no {@link Subscriber} from which to get a contextual discard handler. + *
  • + *
  • + * {@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. + *
  • + *
  • + * {@link EmitResult#FAIL_CANCELLED}: discard the value ({@link Operators#onDiscard(Object, Context)}). + *
  • + *
  • + * {@link EmitResult#FAIL_TERMINATED}: drop the value ({@link Operators#onNextDropped(Object, Context)}). + *
  • + *
  • + * {@link EmitResult#FAIL_NON_SERIALIZED}: throw an {@link EmissionException} mentioning RS spec rule 1.3. + * Note that {@link Sinks#unsafe()} never trigger this result. It would be possible for an {@link EmitFailureHandler} + * to busy-loop and optimistically wait for the contention to disappear to avoid this case for safe sinks... + *
  • + *
+ *

+ * Might throw an unchecked exception as a last resort (eg. in case of a fatal error downstream which cannot + * be propagated to any asynchronous handler, a bubbling exception, a {@link EmitResult#FAIL_NON_SERIALIZED} + * as described above, ...). + * + * @param t the value to emit, not null + * @param failureHandler the failure handler that allows retrying failed {@link EmitResult}. + * @throws EmissionException on non-serialized access + * @see #tryEmitNext(Object) + * @see Subscriber#onNext(Object) + */ + void emitNext(T t, EmitFailureHandler failureHandler); + + /** + * A simplified attempt at completing via the {@link #tryEmitComplete()} API, generating an + * {@link Subscriber#onComplete() onComplete} signal. + * If the result of the attempt is not a {@link EmitResult#isSuccess() success}, implementations SHOULD retry the + * {@link #tryEmitComplete()} call IF the provided {@link EmitFailureHandler} returns {@code true}. + * Otherwise, failures are dealt with in a predefined way that might depend on the actual sink implementation + * (see below for the vanilla reactor-core behavior). + *

+ * Generally, {@link #tryEmitComplete()} is preferable since it allows a custom handling + * of error cases, although this implies checking the returned {@link EmitResult} and correctly + * acting on it. This API is intended as a good default for convenience. + *

+ * When the {@link EmitResult} is not a success, vanilla reactor-core operators have the following behavior: + *

    + *
  • + * {@link EmitResult#FAIL_OVERFLOW}: irrelevant as onComplete is not driven by backpressure. + *
  • + *
  • + * {@link EmitResult#FAIL_ZERO_SUBSCRIBER}: the completion can be ignored since nobody is listening. + * Note that most vanilla reactor sinks never trigger this result for onComplete, replaying the + * terminal signal to later subscribers instead (to the exception of {@link UnicastSpec#onBackpressureError()}). + *
  • + *
  • + * {@link EmitResult#FAIL_CANCELLED}: the completion can be ignored since nobody is interested. + *
  • + *
  • + * {@link EmitResult#FAIL_TERMINATED}: the extra completion is basically ignored since there was a previous + * termination signal, but there is nothing interesting to log. + *
  • + *
  • + * {@link EmitResult#FAIL_NON_SERIALIZED}: throw an {@link EmissionException} mentioning RS spec rule 1.3. + * Note that {@link Sinks#unsafe()} never trigger this result. It would be possible for an {@link EmitFailureHandler} + * to busy-loop and optimistically wait for the contention to disappear to avoid this case in safe sinks... + *
  • + *
+ *

+ * Might throw an unchecked exception as a last resort (eg. in case of a fatal error downstream which cannot + * be propagated to any asynchronous handler, a bubbling exception, a {@link EmitResult#FAIL_NON_SERIALIZED} + * as described above, ...). + * + * @param failureHandler the failure handler that allows retrying failed {@link EmitResult}. + * @throws EmissionException on non-serialized access + * @see #tryEmitComplete() + * @see Subscriber#onComplete() + */ + void emitComplete(EmitFailureHandler failureHandler); + + /** + * A simplified attempt at failing the sequence via the {@link #tryEmitError(Throwable)} API, generating an + * {@link Subscriber#onError(Throwable) onError} signal. + * If the result of the attempt is not a {@link EmitResult#isSuccess() success}, implementations SHOULD retry the + * {@link #tryEmitError(Throwable)} call IF the provided {@link EmitFailureHandler} returns {@code true}. + * Otherwise, failures are dealt with in a predefined way that might depend on the actual sink implementation + * (see below for the vanilla reactor-core behavior). + *

+ * Generally, {@link #tryEmitError(Throwable)} is preferable since it allows a custom handling + * of error cases, although this implies checking the returned {@link EmitResult} and correctly + * acting on it. This API is intended as a good default for convenience. + *

+ * When the {@link EmitResult} is not a success, vanilla reactor-core operators have the following behavior: + *

    + *
  • + * {@link EmitResult#FAIL_OVERFLOW}: irrelevant as onError is not driven by backpressure. + *
  • + *
  • + * {@link EmitResult#FAIL_ZERO_SUBSCRIBER}: the error is ignored since nobody is listening. Note that most vanilla reactor sinks + * never trigger this result for onError, replaying the terminal signal to later subscribers instead + * (to the exception of {@link UnicastSpec#onBackpressureError()}). + *
  • + *
  • + * {@link EmitResult#FAIL_CANCELLED}: the error can be ignored since nobody is interested. + *
  • + *
  • + * {@link EmitResult#FAIL_TERMINATED}: the error unexpectedly follows another terminal signal, so it is + * dropped via {@link Operators#onErrorDropped(Throwable, Context)}. + *
  • + *
  • + * {@link EmitResult#FAIL_NON_SERIALIZED}: throw an {@link EmissionException} mentioning RS spec rule 1.3. + * Note that {@link Sinks#unsafe()} never trigger this result. It would be possible for an {@link EmitFailureHandler} + * to busy-loop and optimistically wait for the contention to disappear to avoid this case in safe sinks... + *
  • + *
+ *

+ * Might throw an unchecked exception as a last resort (eg. in case of a fatal error downstream which cannot + * be propagated to any asynchronous handler, a bubbling exception, a {@link EmitResult#FAIL_NON_SERIALIZED} + * as described above, ...). + * + * @param error the exception to signal, not null + * @param failureHandler the failure handler that allows retrying failed {@link EmitResult}. + * @throws EmissionException on non-serialized access + * @see #tryEmitError(Throwable) + * @see Subscriber#onError(Throwable) + */ + void emitError(Throwable error, EmitFailureHandler failureHandler); + + /** + * Get how many {@link Subscriber Subscribers} are currently subscribed to the sink. + *

+ * This is a best effort peek at the sink state, and a subsequent attempt at emitting + * to the sink might still return {@link EmitResult#FAIL_ZERO_SUBSCRIBER} where relevant. + * (generally in {@link #tryEmitNext(Object)}). Request (and lack thereof) isn't taken + * into account, all registered subscribers are counted. + * + * @return the number of subscribers at the time of invocation + */ + int currentSubscriberCount(); + + /** + * 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} + */ + Flux asFlux(); + } + + /** + * 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. + * + * @param a generic type for the {@link Mono} view, allowing composition + * @author Simon Baslé + * @author Stephane Maldini + */ + public interface Empty extends Scannable { + + /** + * Try to complete the {@link Mono} without a value, generating only an {@link Subscriber#onComplete() onComplete} signal. + * The result of the attempt is represented as an {@link EmitResult}, which possibly indicates error cases. + *

+ * See the list of failure {@link EmitResult} in {@link #emitEmpty(EmitFailureHandler)} javadoc for an + * 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 Subscriber#onComplete() + */ + EmitResult tryEmitEmpty(); + + /** + * Try to fail the {@link Mono}, generating only an {@link Subscriber#onError(Throwable) onError} signal. + * The result of the attempt is represented as an {@link EmitResult}, which possibly indicates error cases. + *

+ * See the list of failure {@link EmitResult} in {@link #emitError(Throwable, EmitFailureHandler)} javadoc for an + * example of how each of these can be dealt with, to decide if the emit API would be a good enough fit instead. + * + * @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 Subscriber#onError(Throwable) + */ + EmitResult tryEmitError(Throwable error); + + /** + * A simplified attempt at completing via the {@link #tryEmitEmpty()} API, generating an + * {@link Subscriber#onComplete() onComplete} signal. + * If the result of the attempt is not a {@link EmitResult#isSuccess() success}, implementations SHOULD retry the + * {@link #tryEmitEmpty()} call IF the provided {@link EmitFailureHandler} returns {@code true}. + * Otherwise, failures are dealt with in a predefined way that might depend on the actual sink implementation + * (see below for the vanilla reactor-core behavior). + *

+ * Generally, {@link #tryEmitEmpty()} is preferable since it allows a custom handling + * of error cases, although this implies checking the returned {@link EmitResult} and correctly + * acting on it. This API is intended as a good default for convenience. + *

+ * When the {@link EmitResult} is not a success, vanilla reactor-core operators have the following behavior: + *

    + *
  • + * {@link EmitResult#FAIL_OVERFLOW}: irrelevant as onComplete is not driven by backpressure. + *
  • + *
  • + * {@link EmitResult#FAIL_ZERO_SUBSCRIBER}: the completion can be ignored since nobody is listening. + * Note that most vanilla reactor sinks never trigger this result for onComplete, replaying the + * terminal signal to later subscribers instead (to the exception of {@link UnicastSpec#onBackpressureError()}). + *
  • + *
  • + * {@link EmitResult#FAIL_CANCELLED}: the completion can be ignored since nobody is interested. + *
  • + *
  • + * {@link EmitResult#FAIL_TERMINATED}: the extra completion is basically ignored since there was a previous + * termination signal, but there is nothing interesting to log. + *
  • + *
  • + * {@link EmitResult#FAIL_NON_SERIALIZED}: throw an {@link EmissionException} mentioning RS spec rule 1.3. + * Note that {@link Sinks#unsafe()} never trigger this result. It would be possible for an {@link EmitFailureHandler} + * to busy-loop and optimistically wait for the contention to disappear to avoid this case in safe sinks... + *
  • + *
+ *

+ * Might throw an unchecked exception as a last resort (eg. in case of a fatal error downstream which cannot + * be propagated to any asynchronous handler, a bubbling exception, a {@link EmitResult#FAIL_NON_SERIALIZED} + * as described above, ...). + * + * @param failureHandler the failure handler that allows retrying failed {@link EmitResult}. + * @throws EmissionException on non-serialized access + * @see #tryEmitEmpty() + * @see Subscriber#onComplete() + */ + void emitEmpty(EmitFailureHandler failureHandler); + + + /** + * A simplified attempt at failing the sequence via the {@link #tryEmitError(Throwable)} API, generating an + * {@link Subscriber#onError(Throwable) onError} signal. + * If the result of the attempt is not a {@link EmitResult#isSuccess() success}, implementations SHOULD retry the + * {@link #tryEmitError(Throwable)} call IF the provided {@link EmitFailureHandler} returns {@code true}. + * Otherwise, failures are dealt with in a predefined way that might depend on the actual sink implementation + * (see below for the vanilla reactor-core behavior). + *

+ * Generally, {@link #tryEmitError(Throwable)} is preferable since it allows a custom handling + * of error cases, although this implies checking the returned {@link EmitResult} and correctly + * acting on it. This API is intended as a good default for convenience. + *

+ * When the {@link EmitResult} is not a success, vanilla reactor-core operators have the following behavior: + *

    + *
  • + * {@link EmitResult#FAIL_OVERFLOW}: irrelevant as onError is not driven by backpressure. + *
  • + *
  • + * {@link EmitResult#FAIL_ZERO_SUBSCRIBER}: the error is ignored since nobody is listening. Note that most vanilla reactor sinks + * never trigger this result for onError, replaying the terminal signal to later subscribers instead + * (to the exception of {@link UnicastSpec#onBackpressureError()}). + *
  • + *
  • + * {@link EmitResult#FAIL_CANCELLED}: the error can be ignored since nobody is interested. + *
  • + *
  • + * {@link EmitResult#FAIL_TERMINATED}: the error unexpectedly follows another terminal signal, so it is + * dropped via {@link Operators#onErrorDropped(Throwable, Context)}. + *
  • + *
  • + * {@link EmitResult#FAIL_NON_SERIALIZED}: throw an {@link EmissionException} mentioning RS spec rule 1.3. + * Note that {@link Sinks#unsafe()} never trigger this result. It would be possible for an {@link EmitFailureHandler} + * to busy-loop and optimistically wait for the contention to disappear to avoid this case in safe sinks... + *
  • + *
+ *

+ * Might throw an unchecked exception as a last resort (eg. in case of a fatal error downstream which cannot + * be propagated to any asynchronous handler, a bubbling exception, a {@link EmitResult#FAIL_NON_SERIALIZED} + * as described above, ...). + * + * @param error the exception to signal, not null + * @param failureHandler the failure handler that allows retrying failed {@link EmitResult}. + * @throws EmissionException on non-serialized access + * @see #tryEmitError(Throwable) + * @see Subscriber#onError(Throwable) + */ + void emitError(Throwable error, EmitFailureHandler failureHandler); + + /** + * Get how many {@link Subscriber Subscribers} are currently subscribed to the sink. + *

+ * This is a best effort peek at the sink state, and a subsequent attempt at emitting + * to the sink might still return {@link EmitResult#FAIL_ZERO_SUBSCRIBER} where relevant. + * Request (and lack thereof) isn't taken into account, all registered subscribers are counted. + * + * @return the number of active subscribers at the time of invocation + */ + int currentSubscriberCount(); + + /** + * 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} + */ + Mono asMono(); + } + + /** + * A base interface for standalone {@link Sinks} with {@link Mono} semantics. + *

+ * The sink can be exposed to consuming code as a {@link Mono} via its {@link #asMono()} view. + * + * @author Simon Baslé + * @author Stephane Maldini + */ + public interface One extends Empty { + + /** + * Try to complete the {@link Mono} with an element, generating an {@link Subscriber#onNext(Object) onNext} signal + * immediately followed by an {@link Subscriber#onComplete() onComplete} signal. A {@code null} value + * will only trigger the onComplete. The result of the attempt is represented as an {@link EmitResult}, + * which possibly indicates error cases. + *

+ * See the list of failure {@link EmitResult} in {@link #emitValue(Object, EmitFailureHandler)} javadoc for an + * example of how each of these can be dealt with, to decide if the emit API would be a good enough fit instead. + *

+ * Might throw an unchecked exception as a last resort (eg. in case of a fatal error downstream which cannot + * be propagated to any asynchronous handler, a bubbling exception, ...). + * + * @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 Subscriber#onNext(Object) + * @see Subscriber#onComplete() + */ + EmitResult tryEmitValue(@Nullable T value); + + /** + * A simplified attempt at emitting a non-null element via the {@link #tryEmitValue(Object)} API, generating an + * {@link Subscriber#onNext(Object) onNext} signal immediately followed by an {@link Subscriber#onComplete()} signal. + * If the result of the attempt is not a {@link EmitResult#isSuccess() success}, implementations SHOULD retry the + * {@link #tryEmitValue(Object)} call IF the provided {@link EmitFailureHandler} returns {@code true}. + * Otherwise, failures are dealt with in a predefined way that might depend on the actual sink implementation + * (see below for the vanilla reactor-core behavior). + *

+ * Generally, {@link #tryEmitValue(Object)} is preferable since it allows a custom handling + * of error cases, although this implies checking the returned {@link EmitResult} and correctly + * acting on it. This API is intended as a good default for convenience. + *

+ * When the {@link EmitResult} is not a success, vanilla reactor-core operators have the following behavior: + *

    + *
  • + * {@link EmitResult#FAIL_ZERO_SUBSCRIBER}: no particular handling. should ideally discard the value but at that + * point there's no {@link Subscriber} from which to get a contextual discard handler. + *
  • + *
  • + * {@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. + *
  • + *
  • + * {@link EmitResult#FAIL_CANCELLED}: discard the value ({@link Operators#onDiscard(Object, Context)}). + *
  • + *
  • + * {@link EmitResult#FAIL_TERMINATED}: drop the value ({@link Operators#onNextDropped(Object, Context)}). + *
  • + *
  • + * {@link EmitResult#FAIL_NON_SERIALIZED}: throw an {@link EmissionException} mentioning RS spec rule 1.3. + * Note that {@link Sinks#unsafe()} never trigger this result. It would be possible for an {@link EmitFailureHandler} + * to busy-loop and optimistically wait for the contention to disappear to avoid this case for safe sinks... + *
  • + *
+ *

+ * Might throw an unchecked exception as a last resort (eg. in case of a fatal error downstream which cannot + * be propagated to any asynchronous handler, a bubbling exception, a {@link EmitResult#FAIL_NON_SERIALIZED} + * as described above, ...). + * + * @param value the value to emit and complete with, a {@code null} is actually acceptable to only trigger an onComplete + * @param failureHandler the failure handler that allows retrying failed {@link EmitResult}. + * @throws EmissionException on non-serialized access + * @see #tryEmitValue(Object) + * @see Subscriber#onNext(Object) + * @see Subscriber#onComplete() + */ + void emitValue(@Nullable T value, EmitFailureHandler failureHandler); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SinksSpecs.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SinksSpecs.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SinksSpecs.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2020-2021 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.Queue; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import reactor.core.Disposable; +import reactor.core.publisher.Sinks.Empty; +import reactor.core.publisher.Sinks.Many; +import reactor.core.publisher.Sinks.One; +import reactor.core.scheduler.Scheduler; + +final class SinksSpecs { + + static final Sinks.RootSpec UNSAFE_ROOT_SPEC = new RootSpecImpl(false); + static final Sinks.RootSpec DEFAULT_ROOT_SPEC = new RootSpecImpl(true); + + abstract static class AbstractSerializedSink { + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(AbstractSerializedSink.class, "wip"); + + volatile Thread lockedAt; + static final AtomicReferenceFieldUpdater LOCKED_AT = + AtomicReferenceFieldUpdater.newUpdater(AbstractSerializedSink.class, Thread.class, "lockedAt"); + + boolean tryAcquire(Thread currentThread) { + if (WIP.get(this) == 0 && WIP.compareAndSet(this, 0, 1)) { + // lazySet in thread A here is ok because: + // 1. initial state is `null` + // 2. `LOCKED_AT.get(this) != currentThread` from a different thread B could see outdated null or an outdated old thread + // 3. but that old thread cannot be B: since we're in thread B, it must have executed the compareAndSet which would have loaded the update from A + // 4. Seeing `null` or `C` is equivalent from seeing `A` from the perspective of the condition (`!= currentThread` is still true in all three cases) + LOCKED_AT.lazySet(this, currentThread); + } + else { + if (LOCKED_AT.get(this) != currentThread) { + return false; + } + WIP.incrementAndGet(this); + } + return true; + } + } + + static final class RootSpecImpl implements Sinks.RootSpec, + Sinks.ManySpec, + Sinks.MulticastSpec, + Sinks.MulticastReplaySpec { + + final boolean serialized; + 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); + } + + & ContextHolder> Empty wrapEmpty(EMPTY original) { + if (serialized) { + return new SinkEmptySerialized<>(original, original); + } + return original; + } + + & ContextHolder> One wrapOne(ONE original) { + if (serialized) { + return new SinkOneSerialized<>(original, original); + } + return original; + } + + & ContextHolder> Many wrapMany(MANY original) { + if (serialized) { + return new SinkManySerialized<>(original, original); + } + return original; + } + + @Override + public Sinks.ManySpec many() { + return this; + } + + @Override + public Empty empty() { + return wrapEmpty(new SinkEmptyMulticast<>()); + } + + @Override + public One one() { + return wrapOne(new SinkOneMulticast<>()); + } + + @Override + public Sinks.UnicastSpec unicast() { + return this.unicastSpec; + } + + @Override + public Sinks.MulticastSpec multicast() { + return this; + } + + @Override + public Sinks.MulticastReplaySpec replay() { + return this; + } + + @Override + public Many onBackpressureBuffer() { + @SuppressWarnings("deprecation") // EmitterProcessor will be removed in 3.5. + final EmitterProcessor original = EmitterProcessor.create(); + return wrapMany(original); + } + + @Override + public Many onBackpressureBuffer(int bufferSize) { + @SuppressWarnings("deprecation") // EmitterProcessor will be removed in 3.5. + final EmitterProcessor original = EmitterProcessor.create(bufferSize); + return wrapMany(original); + } + + @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); + } + + @Override + public Many directAllOrNothing() { + final SinkManyBestEffort original = SinkManyBestEffort.createAllOrNothing(); + return wrapMany(original); + } + + @Override + public Many directBestEffort() { + final SinkManyBestEffort original = SinkManyBestEffort.createBestEffort(); + return wrapMany(original); + } + + + @Override + public Many all() { + @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. + final ReplayProcessor original = ReplayProcessor.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); + return wrapMany(original); + } + + @Override + public Many latest() { + @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. + final ReplayProcessor original = ReplayProcessor.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); + return wrapMany(original); + } + + @Override + public Many limit(int historySize) { + 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); + return wrapMany(original); + } + + @Override + public Many limit(Duration maxAge) { + @SuppressWarnings("deprecation") // ReplayProcessor will be removed in 3.5. + final ReplayProcessor original = ReplayProcessor.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); + return wrapMany(original); + } + + @Override + public Many limit(int historySize, Duration maxAge) { + 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); + return wrapMany(original); + } + + @Override + public Many limit(int historySize, Duration maxAge, Scheduler scheduler) { + 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); + return wrapMany(original); + } + } + + static final class UnicastSpecImpl implements Sinks.UnicastSpec { + + final boolean serialized; + + UnicastSpecImpl(boolean serialized) { + this.serialized = serialized; + } + + & ContextHolder> Many wrapMany(MANY original) { + if (serialized) { + return new SinkManySerialized<>(original, original); + } + return original; + } + + @Override + public Many onBackpressureBuffer() { + @SuppressWarnings("deprecation") // UnicastProcessor will be removed in 3.5. + final UnicastProcessor original = UnicastProcessor.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); + 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); + return wrapMany(original); + } + + @Override + public Many onBackpressureError() { + final UnicastManySinkNoBackpressure original = UnicastManySinkNoBackpressure.create(); + return wrapMany(original); + } + } +} + Index: 3rdParty_sources/reactor/reactor/core/publisher/SourceProducer.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SourceProducer.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SourceProducer.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016-2021 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.Publisher; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * + * {@link SourceProducer} is a {@link Publisher} that is {@link Scannable} for the + * purpose of being tied back to a chain of operators. By itself it doesn't allow + * walking the hierarchy of operators, as they can only be tied from downstream to upstream + * by referencing their sources. + * + * @param output operator produced type + * + * @author Simon Baslé + */ +interface SourceProducer extends Scannable, Publisher { + + @Override + @Nullable + default Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) return Scannable.from(null); + if (key == Attr.ACTUAL) return Scannable.from(null); + + return null; + } + + @Override + default String stepName() { + return "source(" + getClass().getSimpleName() + ")"; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/StrictSubscriber.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/StrictSubscriber.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/StrictSubscriber.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2017-2021 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.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +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; + +/** + * Reactive Streams Commons safe exit + */ +final class StrictSubscriber implements Scannable, CoreSubscriber, Subscription { + + final Subscriber actual; + + volatile Subscription s; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater S = + AtomicReferenceFieldUpdater.newUpdater(StrictSubscriber.class, + Subscription.class, + "s"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(StrictSubscriber.class, "requested"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(StrictSubscriber.class, "wip"); + + volatile Throwable error; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERROR = + AtomicReferenceFieldUpdater.newUpdater(StrictSubscriber.class, + Throwable.class, + "error"); + + volatile boolean done; + + StrictSubscriber(Subscriber actual) { + this.actual = actual; + } + + @Override + public void onSubscribe(Subscription s) { + if (Operators.validate(this.s, s)) { + + actual.onSubscribe(this); + + if (Operators.setOnce(S, this, s)) { + long r = REQUESTED.getAndSet(this, 0L); + if (r != 0L) { + s.request(r); + } + } + } + else { + onError(new IllegalStateException("§2.12 violated: onSubscribe must be called at most once")); + } + } + + @Override + public void onNext(T t) { + if (WIP.get(this) == 0 && WIP.compareAndSet(this, 0, 1)) { + actual.onNext(t); + if (WIP.decrementAndGet(this) != 0) { + Throwable ex = Exceptions.terminate(ERROR, this); + if (ex != null) { + actual.onError(ex); + } else { + actual.onComplete(); + } + } + } + } + + @Override + public void onError(Throwable t) { + done = true; + if (Exceptions.addThrowable(ERROR, this, t)) { + if (WIP.getAndIncrement(this) == 0) { + actual.onError(Exceptions.terminate(ERROR, this)); + } + } + else { + Operators.onErrorDropped(t, Context.empty()); + } + } + + @Override + public void onComplete() { + done = true; + if (WIP.getAndIncrement(this) == 0) { + Throwable ex = Exceptions.terminate(ERROR, this); + if (ex != null) { + actual.onError(ex); + } + else { + actual.onComplete(); + } + } + } + + @Override + public void request(long n) { + if (n <= 0) { + cancel(); + onError(new IllegalArgumentException( + "§3.9 violated: positive request amount required but it was " + n)); + return; + } + Subscription a = s; + if (a != null) { + a.request(n); + } + else { + Operators.addCap(REQUESTED, this, n); + a = s; + if (a != null) { + long r = REQUESTED.getAndSet(this, 0L); + if (r != 0L) { + a.request(n); + } + } + } + } + + @Override + public void cancel() { + if(!done) { + Operators.terminate(S, this); + } + } + + @Override + @Nullable + public Object scanUnsafe(Attr key) { + if (key == Attr.PARENT) { + return s; + } + if (key == Attr.CANCELLED) { + return s == Operators.cancelledSubscription(); + } + if (key == Attr.REQUESTED_FROM_DOWNSTREAM) { + return requested; + } + if (key == Attr.ACTUAL) { + return actual; + } + + return null; + } + + @Override + public Context currentContext() { + return Context.empty(); + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/SynchronousSink.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/SynchronousSink.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/SynchronousSink.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016-2021 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.Function; + +import org.reactivestreams.Subscriber; +import reactor.core.CoreSubscriber; +import reactor.util.context.Context; + +/** + * Interface to produce synchronously "one signal" to an underlying {@link Subscriber}. + *

+ * At most one {@link #next} call and/or one {@link #complete()} or + * {@link #error(Throwable)} should be called per invocation of the generator function. + * + *

+ * Calling a {@link SynchronousSink} outside of a generator consumer or function, e.g. + * using an async callback, is forbidden. You can {@link FluxSink} or + * {@link MonoSink} based generators for these situations. + * + * @param the output value type + */ +public interface SynchronousSink { + /** + * @see Subscriber#onComplete() + */ + void complete(); + + /** + * Return the current subscriber {@link Context}. + *

+ * {@link Context} can be enriched via {@link Mono#contextWrite(Function)} + * or {@link Flux#contextWrite(Function)} + * operator or directly by a child subscriber overriding + * {@link CoreSubscriber#currentContext()} + * + * @return the current subscriber {@link Context}. + */ + Context currentContext(); + + /** + * @param e the exception to signal, not null + * + * @see Subscriber#onError(Throwable) + */ + void error(Throwable e); + + /** + * Try emitting, might throw an unchecked exception. + * + * @param t the value to emit, not null + * + * @throws RuntimeException in case of unchecked error during the emission + * @see Subscriber#onNext(Object) + */ + void next(T t); +} Index: 3rdParty_sources/reactor/reactor/core/publisher/Timed.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/Timed.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/Timed.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020-2021 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.time.Instant; +import java.util.function.Supplier; + +/** + * @author Simon Baslé + */ +public interface Timed extends Supplier { + + /** + * Get the value wrapped by this {@link Timed}. + * + * @return the onNext value that was timed + */ + @Override + T get(); + + /** + * Get the elapsed {@link Duration} since the previous onNext (or onSubscribe in + * case this represents the first onNext). If possible, nanosecond resolution is used. + * + * @return the elapsed {@link Duration} since the previous onNext + */ + Duration elapsed(); + + /** + * Get the elapsed {@link Duration} since the subscription (onSubscribe signal). + * If possible, nanosecond resolution is used. + * + * @return the elapsed {@link Duration} since subscription + */ + Duration elapsedSinceSubscription(); + + /** + * Get the timestamp of the emission of this timed onNext, as an {@link Instant}. + * It has the same resolution as {@link Instant#ofEpochMilli(long)}. + * + * @return the epoch timestamp {@link Instant} for this timed onNext + */ + Instant timestamp(); + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/Traces.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/Traces.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/Traces.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2018-2021 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.Iterator; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import sun.misc.JavaLangAccess; +import sun.misc.SharedSecrets; + +/** + * Utilities around manipulating stack traces and displaying assembly traces. + * + * @author Simon Baslé + * @author Sergei Egorov + */ +final class Traces { + + /** + * If set to true, the creation of FluxOnAssembly will capture the raw stacktrace + * instead of the sanitized version. + */ + static final boolean full = Boolean.parseBoolean(System.getProperty( + "reactor.trace.assembly.fullstacktrace", + "false")); + + static final String CALL_SITE_GLUE = " ⇢ "; + + /** + * Transform the current stack trace into a {@link String} representation, + * each element being prepended with a tabulation and appended with a + * newline. + */ + static Supplier> callSiteSupplierFactory; + + static { + String[] strategyClasses = { + Traces.class.getName() + "$StackWalkerCallSiteSupplierFactory", + Traces.class.getName() + "$SharedSecretsCallSiteSupplierFactory", + Traces.class.getName() + "$ExceptionCallSiteSupplierFactory", + }; + // find one available call-site supplier w.r.t. the jdk version to provide + // linkage-compatibility between jdk 8 and 9+ + callSiteSupplierFactory = Stream + .of(strategyClasses) + .flatMap(className -> { + try { + Class clazz = Class.forName(className); + @SuppressWarnings("unchecked") + Supplier> function = (Supplier) clazz.getDeclaredConstructor() + .newInstance(); + return Stream.of(function); + } + // explicitly catch LinkageError to support static code analysis + // tools detect the attempt at finding out jdk environment + catch (LinkageError e) { + return Stream.empty(); + } + catch (Throwable e) { + return Stream.empty(); + } + }) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Valid strategy not found")); + } + + /** + * Utility class for the call-site extracting on Java 9+. + * + */ + @SuppressWarnings("unused") + static final class StackWalkerCallSiteSupplierFactory implements Supplier> { + + static { + // Trigger eager StackWalker class loading. + StackWalker.getInstance(); + } + + /** + * Transform the current stack trace into a {@link String} representation, + * each element being prepended with a tabulation and appended with a + * newline. + * + * @return the string version of the stacktrace. + */ + @Override + public Supplier get() { + StackWalker.StackFrame[] stack = StackWalker.getInstance().walk(s -> { + StackWalker.StackFrame[] result = new StackWalker.StackFrame[10]; + Iterator iterator = s.iterator(); + iterator.next(); // .get + + int i = 0; + while (iterator.hasNext()) { + StackWalker.StackFrame frame = iterator.next(); + + if (i >= result.length) { + return new StackWalker.StackFrame[0]; + } + + result[i++] = frame; + + if (isUserCode(frame.getClassName())) { + break; + } + } + StackWalker.StackFrame[] copy = new StackWalker.StackFrame[i]; + System.arraycopy(result, 0, copy, 0, i); + return copy; + }); + + if (stack.length == 0) { + return () -> ""; + } + + if (stack.length == 1) { + return () -> "\t" + stack[0].toString() + "\n"; + } + + return () -> { + StringBuilder sb = new StringBuilder(); + + for (int j = stack.length - 2; j > 0; j--) { + StackWalker.StackFrame previous = stack[j]; + + if (!full) { + if (previous.isNativeMethod()) { + continue; + } + + String previousRow = previous.getClassName() + "." + previous.getMethodName(); + if (shouldSanitize(previousRow)) { + continue; + } + } + sb.append("\t") + .append(previous.toString()) + .append("\n"); + break; + } + + sb.append("\t") + .append(stack[stack.length - 1].toString()) + .append("\n"); + + return sb.toString(); + }; + } + } + + @SuppressWarnings("unused") + static class SharedSecretsCallSiteSupplierFactory implements Supplier> { + + @Override + public Supplier get() { + return new TracingException(); + } + + static class TracingException extends Throwable implements Supplier { + + static final JavaLangAccess javaLangAccess = SharedSecrets.getJavaLangAccess(); + + @Override + public String get() { + int stackTraceDepth = javaLangAccess.getStackTraceDepth(this); + + StackTraceElement previousElement = null; + // Skip get() + for (int i = 2; i < stackTraceDepth; i++) { + StackTraceElement e = javaLangAccess.getStackTraceElement(this, i); + + String className = e.getClassName(); + if (isUserCode(className)) { + StringBuilder sb = new StringBuilder(); + + if (previousElement != null) { + sb.append("\t").append(previousElement.toString()).append("\n"); + } + sb.append("\t").append(e.toString()).append("\n"); + return sb.toString(); + } + else { + if (!full && e.getLineNumber() <= 1) { + continue; + } + + String classAndMethod = className + "." + e.getMethodName(); + if (!full && shouldSanitize(classAndMethod)) { + continue; + } + previousElement = e; + } + } + + return ""; + } + } + } + + @SuppressWarnings("unused") + static class ExceptionCallSiteSupplierFactory implements Supplier> { + + @Override + public Supplier get() { + return new TracingException(); + } + + static class TracingException extends Throwable implements Supplier { + + @Override + public String get() { + StackTraceElement previousElement = null; + StackTraceElement[] stackTrace = getStackTrace(); + // Skip get() + for (int i = 2; i < stackTrace.length; i++) { + StackTraceElement e = stackTrace[i]; + + String className = e.getClassName(); + if (isUserCode(className)) { + StringBuilder sb = new StringBuilder(); + + if (previousElement != null) { + sb.append("\t").append(previousElement.toString()).append("\n"); + } + sb.append("\t").append(e.toString()).append("\n"); + return sb.toString(); + } + else { + if (!full && e.getLineNumber() <= 1) { + continue; + } + + String classAndMethod = className + "." + e.getMethodName(); + if (!full && shouldSanitize(classAndMethod)) { + continue; + } + previousElement = e; + } + } + + return ""; + } + } + } + + /** + * Return true for strings (usually from a stack trace element) that should be + * sanitized out by {@link Traces#callSiteSupplierFactory}. + * + * @param stackTraceRow the row to check + * @return true if it should be sanitized out, false if it should be kept + */ + static boolean shouldSanitize(String stackTraceRow) { + return stackTraceRow.startsWith("java.util.function") + || stackTraceRow.startsWith("reactor.core.publisher.Mono.onAssembly") + || stackTraceRow.equals("reactor.core.publisher.Mono.onAssembly") + || stackTraceRow.equals("reactor.core.publisher.Flux.onAssembly") + || stackTraceRow.equals("reactor.core.publisher.ParallelFlux.onAssembly") + || stackTraceRow.startsWith("reactor.core.publisher.SignalLogger") + || stackTraceRow.startsWith("reactor.core.publisher.FluxOnAssembly") + || stackTraceRow.startsWith("reactor.core.publisher.MonoOnAssembly.") + || stackTraceRow.startsWith("reactor.core.publisher.MonoCallableOnAssembly.") + || stackTraceRow.startsWith("reactor.core.publisher.FluxCallableOnAssembly.") + || stackTraceRow.startsWith("reactor.core.publisher.Hooks") + || stackTraceRow.startsWith("sun.reflect") + || stackTraceRow.startsWith("java.util.concurrent.ThreadPoolExecutor") + || stackTraceRow.startsWith("java.lang.reflect"); + } + + /** + * Extract operator information out of an assembly stack trace in {@link String} form + * (see {@link Traces#callSiteSupplierFactory}). + *

+ * Most operators will result in a line of the form {@code "Flux.map ⇢ user.code.Class.method(Class.java:123)"}, + * that is: + *

    + *
  1. The top of the stack is inspected for Reactor API references, and the deepest + * one is kept, since multiple API references generally denote an alias operator. + * (eg. {@code "Flux.map"})
  2. + *
  3. The next stacktrace element is considered user code and is appended to the + * result with a {@code ⇢} separator. (eg. {@code " ⇢ user.code.Class.method(Class.java:123)"})
  4. + *
  5. If no user code is found in the sanitized stack, then the API reference is outputed in the later format only.
  6. + *
  7. If the sanitized stack is empty, returns {@code "[no operator assembly information]"}
  8. + *
+ * + * + * @param source the sanitized assembly stacktrace in String format. + * @return a {@link String} representing operator and operator assembly site extracted + * from the assembly stack trace. + */ + static String extractOperatorAssemblyInformation(String source) { + String[] parts = extractOperatorAssemblyInformationParts(source); + switch (parts.length) { + case 0: + return "[no operator assembly information]"; + default: + return String.join(CALL_SITE_GLUE, parts); + } + } + + static boolean isUserCode(String line) { + return !line.startsWith("reactor.core.publisher") || line.contains("Test"); + } + + /** + * Extract operator information out of an assembly stack trace in {@link String} form + * (see {@link Traces#callSiteSupplierFactory}) which potentially + * has a header line that one can skip by setting {@code skipFirst} to {@code true}. + *

+ * Most operators will result in a line of the form {@code "Flux.map ⇢ user.code.Class.method(Class.java:123)"}, + * that is: + *

    + *
  1. The top of the stack is inspected for Reactor API references, and the deepest + * one is kept, since multiple API references generally denote an alias operator. + * (eg. {@code "Flux.map"})
  2. + *
  3. The next stacktrace element is considered user code and is appended to the + * result with a {@code ⇢} separator. (eg. {@code " ⇢ user.code.Class.method(Class.java:123)"})
  4. + *
  5. If no user code is found in the sanitized stack, then the API reference is outputed in the later format only.
  6. + *
  7. If the sanitized stack is empty, returns {@code "[no operator assembly information]"}
  8. + *
+ * + * + * @param source the sanitized assembly stacktrace in String format. + * @return a {@link String} representing operator and operator assembly site extracted + * from the assembly stack trace. + */ + static String[] extractOperatorAssemblyInformationParts(String source) { + String[] uncleanTraces = source.split("\n"); + final List traces = Stream.of(uncleanTraces) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + + if (traces.isEmpty()) { + return new String[0]; + } + + int i = 0; + while (i < traces.size() && !isUserCode(traces.get(i))) { + i++; + } + + String apiLine; + String userCodeLine; + if (i == 0) { + //no line was a reactor API line + apiLine = ""; + userCodeLine = traces.get(0); + } + else if (i == traces.size()) { + //we skipped ALL lines, meaning they're all reactor API lines. We'll fully display the last one + apiLine = ""; + userCodeLine = traces.get(i-1).replaceFirst("reactor.core.publisher.", ""); + } + else { + //currently on user code line, previous one is API + apiLine = traces.get(i - 1); + userCodeLine = traces.get(i); + } + + //now we want something in the form "Flux.map ⇢ user.code.Class.method(Class.java:123)" + if (apiLine.isEmpty()) return new String[] { userCodeLine }; + + int linePartIndex = apiLine.indexOf('('); + if (linePartIndex > 0) { + apiLine = apiLine.substring(0, linePartIndex); + } + apiLine = apiLine.replaceFirst("reactor.core.publisher.", ""); + + return new String[] { apiLine, "at " + userCodeLine }; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/UnicastManySinkNoBackpressure.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/UnicastManySinkNoBackpressure.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/UnicastManySinkNoBackpressure.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2020-2021 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 UnicastManySinkNoBackpressure extends Flux implements InternalManySink, Subscription, ContextHolder { + + public static UnicastManySinkNoBackpressure create() { + return new UnicastManySinkNoBackpressure<>(); + } + + enum State { + INITIAL, + SUBSCRIBED, + TERMINATED, + CANCELLED, + } + + volatile State state; + + @SuppressWarnings("rawtypes") + private static final AtomicReferenceFieldUpdater STATE = AtomicReferenceFieldUpdater.newUpdater( + UnicastManySinkNoBackpressure.class, + State.class, + "state" + ); + + private volatile CoreSubscriber actual = null; + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(UnicastManySinkNoBackpressure.class, "requested"); + + UnicastManySinkNoBackpressure() { + 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 Sinks.EmitResult tryEmitNext(T t) { + Objects.requireNonNull(t, "t"); + + switch (state) { + case INITIAL: + return Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER; + case SUBSCRIBED: + if (requested == 0L) { + return Sinks.EmitResult.FAIL_OVERFLOW; + } + + actual.onNext(t); + Operators.produced(REQUESTED, this, 1); + return Sinks.EmitResult.OK; + case TERMINATED: + return Sinks.EmitResult.FAIL_TERMINATED; + case CANCELLED: + return Sinks.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 Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER; + case SUBSCRIBED: + if (STATE.compareAndSet(this, s, State.TERMINATED)) { + actual.onError(t); + actual = null; + return Sinks.EmitResult.OK; + } + continue; + case TERMINATED: + return Sinks.EmitResult.FAIL_TERMINATED; + case CANCELLED: + return Sinks.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 Sinks.EmitResult.FAIL_ZERO_SUBSCRIBER; + case SUBSCRIBED: + if (STATE.compareAndSet(this, s, State.TERMINATED)) { + actual.onComplete(); + actual = null; + return EmitResult.OK; + } + continue; + case TERMINATED: + return Sinks.EmitResult.FAIL_TERMINATED; + case CANCELLED: + return Sinks.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; + } +} Index: 3rdParty_sources/reactor/reactor/core/publisher/UnicastProcessor.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/UnicastProcessor.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/UnicastProcessor.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,652 @@ +/* + * Copyright (c) 2016-2021 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.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; +import java.util.stream.Stream; + +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; + +/** + * A Processor implementation that takes a custom queue and allows + * only a single subscriber. UnicastProcessor 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: UnicastProcessor does not respect the actual subscriber's + * demand as it is described in + * Reactive Streams Spec. However, + * UnicastProcessor embraces configurable Queue internally which allows enabling + * backpressure support and preventing of consumer's overwhelming. + * + * Hence, interaction model between producers and UnicastProcessor will be PUSH + * only. In opposite, interaction model between UnicastProcessor 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, + * UnicastProcessor 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 + * @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()}. + */ +@Deprecated +public final class UnicastProcessor extends FluxProcessor + implements Fuseable.QueueSubscription, Fuseable, InnerOperator, + InternalManySink { + + /** + * Create a new {@link UnicastProcessor} that will buffer on an internal queue in an + * unbounded fashion. + * + * @param the relayed type + * @return a unicast {@link FluxProcessor} + * @deprecated use {@link Sinks.UnicastSpec#onBackpressureBuffer() Sinks.many().unicast().onBackpressureBuffer()} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static UnicastProcessor create() { + return new UnicastProcessor<>(Queues.unbounded().get()); + } + + /** + * Create a new {@link UnicastProcessor} that will buffer on a provided queue in an + * unbounded fashion. + * + * @param queue the buffering queue + * @param the relayed type + * @return a unicast {@link FluxProcessor} + * @deprecated use {@link Sinks.UnicastSpec#onBackpressureBuffer(Queue) Sinks.many().unicast().onBackpressureBuffer(queue)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static UnicastProcessor create(Queue queue) { + return new UnicastProcessor<>(Hooks.wrapQueue(queue)); + } + + /** + * Create a new {@link UnicastProcessor} 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 FluxProcessor} + * @deprecated use {@link Sinks.UnicastSpec#onBackpressureBuffer(Queue, Disposable) Sinks.many().unicast().onBackpressureBuffer(queue, endCallback)} + * (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5. + */ + @Deprecated + public static UnicastProcessor create(Queue queue, Disposable endcallback) { + return new UnicastProcessor<>(Hooks.wrapQueue(queue), endcallback); + } + + /** + * Create a new {@link UnicastProcessor} that will buffer on a provided queue in an + * unbounded fashion. + * + * @param queue the buffering queue + * @param endcallback called on any terminal signal + * @param onOverflow called when queue.offer return false and unicastProcessor is + * about to emit onError. + * @param the relayed type + * + * @return a unicast {@link FluxProcessor} + * @deprecated use {@link Sinks.UnicastSpec#onBackpressureBuffer(Queue, Disposable) Sinks.many().unicast().onBackpressureBuffer(queue, endCallback)} + * (or the unsafe variant if you're sure about external synchronization). The {@code onOverflow} callback is not + * supported anymore. To be removed in 3.5. + */ + @Deprecated + public static UnicastProcessor create(Queue queue, + Consumer onOverflow, + Disposable endcallback) { + return new UnicastProcessor<>(Hooks.wrapQueue(queue), onOverflow, endcallback); + } + + final Queue queue; + final Consumer onOverflow; + + volatile Disposable onTerminate; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ON_TERMINATE = + AtomicReferenceFieldUpdater.newUpdater(UnicastProcessor.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(UnicastProcessor.class, "once"); + + volatile int wip; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(UnicastProcessor.class, "wip"); + + volatile int discardGuard; + @SuppressWarnings("rawtypes") + static final AtomicIntegerFieldUpdater DISCARD_GUARD = + AtomicIntegerFieldUpdater.newUpdater(UnicastProcessor.class, "discardGuard"); + + volatile long requested; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater REQUESTED = + AtomicLongFieldUpdater.newUpdater(UnicastProcessor.class, "requested"); + + boolean outputFused; + + public UnicastProcessor(Queue queue) { + this.queue = Objects.requireNonNull(queue, "queue"); + this.onTerminate = null; + this.onOverflow = null; + } + + public UnicastProcessor(Queue queue, Disposable onTerminate) { + this.queue = Objects.requireNonNull(queue, "queue"); + this.onTerminate = Objects.requireNonNull(onTerminate, "onTerminate"); + this.onOverflow = null; + } + + @Deprecated + public UnicastProcessor(Queue queue, + Consumer onOverflow, + Disposable onTerminate) { + this.queue = Objects.requireNonNull(queue, "queue"); + this.onOverflow = Objects.requireNonNull(onOverflow, "onOverflow"); + this.onTerminate = Objects.requireNonNull(onTerminate, "onTerminate"); + } + + @Override + public int getBufferSize() { + return Queues.capacity(this.queue); + } + + @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.PREFETCH == key) return Integer.MAX_VALUE; + if (Attr.CANCELLED == key) return cancelled; + + //TERMINATED and ERROR covered in super + return super.scanUnsafe(key); + } + + @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; + } + if (cancelled) { + return EmitResult.FAIL_CANCELLED; + } + + done = true; + + doTerminate(); + + drain(null); + return Sinks.EmitResult.OK; + } + + @Override + public void onError(Throwable throwable) { + emitError(throwable, Sinks.EmitFailureHandler.FAIL_FAST); + } + + @Override + public Sinks.EmitResult tryEmitError(Throwable t) { + if (done) { + return Sinks.EmitResult.FAIL_TERMINATED; + } + if (cancelled) { + return EmitResult.FAIL_CANCELLED; + } + + error = t; + done = true; + + doTerminate(); + + drain(null); + return EmitResult.OK; + } + + @Override + public void onNext(T t) { + emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST); + } + + @Override + public void emitNext(T value, Sinks.EmitFailureHandler failureHandler) { + if (onOverflow == null) { + InternalManySink.super.emitNext(value, failureHandler); + return; + } + + // TODO consider deprecating onOverflow and suggesting using a strategy instead + InternalManySink.super.emitNext( + value, (signalType, emission) -> { + boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_NEXT, emission); + if (!shouldRetry) { + switch (emission) { + case FAIL_ZERO_SUBSCRIBER: + case FAIL_OVERFLOW: + try { + onOverflow.accept(value); + } + catch (Throwable e) { + Exceptions.throwIfFatal(e); + emitError(e, Sinks.EmitFailureHandler.FAIL_FAST); + } + break; + } + } + return shouldRetry; + } + ); + } + + @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; + } + + @Override + protected boolean isIdentityProcessor() { + return true; + } + + 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 void onSubscribe(Subscription s) { + if (done || cancelled) { + s.cancel(); + } else { + s.request(Long.MAX_VALUE); + } + } + + @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("UnicastProcessor " + + "allows only 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 boolean isDisposed() { + return cancelled || done; + } + + @Override + public boolean isTerminated() { + return done; + } + + @Override + @Nullable + public Throwable getError() { + return error; + } + + @Override + public CoreSubscriber actual() { + return actual; + } + + @Override + public long downstreamCount() { + return hasDownstreams() ? 1L : 0L; + } + + @Override + public boolean hasDownstreams() { + return hasDownstream; + } + +} Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/all.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/all.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/all.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + request(unbounded ) + + + + + + cancel() + + + + + + + + all ( + + + ) + + + + + + + + isCircle( + + + + + + + + ) + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/and.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/and.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/and.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + and ( + + + ) + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/any.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/any.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/any.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + request(unbounded ) + + + + + + cancel() + + + + + + + + + + + + any ( + + + ) + + + + + + + + isCircle( + + + + + + + + ) + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/autoConnect.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/autoConnect.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/autoConnect.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + autoConnect + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + + + + + + connect + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/autoConnectWithMinSubscribers.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/autoConnectWithMinSubscribers.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/autoConnectWithMinSubscribers.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + + + + + + autoConnect(minSubscribers=2 ) + + + + + + + + + + + + + connect + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/block.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/block.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/block.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + block + + + + subscribe() + + + + subscribe() + + + + + + + + return + + + ; + + + + + + + + return + + + ; + + + + + + + null + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockFirst.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockFirst.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockFirst.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + blockFirst + + + + subscribe() + + + + subscribe() + + + + + + + + cancel() + + + + + + return + + + ; + + + + + + + + + return + + + ; + + + + + + null + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockFirstWithTimeout.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockFirstWithTimeout.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockFirstWithTimeout.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + + cancel() + + + + + + + + subscribe() + + + + + + blockFirst ( + + + ) + + + + + + return + + + ; + + + + + + + + + + + + + + return + + + ; + + + + + null + + + + throw + + + ; + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockLast.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockLast.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockLast.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + blockLast + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + return + + + ; + + + + + + return + + + ; + + + + null + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockLastWithTimeout.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockLastWithTimeout.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockLastWithTimeout.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + + + + subscribe() + + + + blockLast ( + + + ) + + + + + + return + + + ; + + + + + + + + + return + + + ; + + + null + + + + throw + + + ; + + + + + + + + + + + + + + + + + + + cancel() + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockOptional.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockOptional.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockOptional.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + blockOptional + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + return + + + ; + + + + + + + + + + ; + + + Optional.of( + + + ) + + + return + + + Optional.empty() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockOptionalWithTimeout.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockOptionalWithTimeout.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockOptionalWithTimeout.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + + + + + subscribe() + + + + blockOptional ( + + + ) + + + + + + ; + + + + + + + + + + + + + + return + + + ; + + + + + Optional.empty() + + + + throw + + + ; + + + + + + + + + + cancel() + + + + + ) + + + + + + + + + + + + + + + + + + + return + + + Optional.of( + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockWithTimeout.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockWithTimeout.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/blockWithTimeout.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + + + + + subscribe() + + + + block ( + + + ) + + + + + + return + + + ; + + + + + + + + + + + + + + return + + + ; + + + + + null + + + + throw + + + ; + + + + + + + + + + cancel() + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/buffer.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/buffer.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/buffer.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + buffer + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferTimeoutWithMaxSizeAndTimespan.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferTimeoutWithMaxSizeAndTimespan.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferTimeoutWithMaxSizeAndTimespan.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,923 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + bufferTimeout ( + maxSize = 2 + , + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntil.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntil.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntil.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bufferUntil ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntilChanged.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntilChanged.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntilChanged.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + bufferUntilChanged + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntilChangedWithKey.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntilChangedWithKey.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntilChangedWithKey.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bufferUntilChanged ( + + + + + + + + + + + + + ) + + + ) + + + color( + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntilWithCutBefore.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntilWithCutBefore.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferUntilWithCutBefore.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bufferUntil ( + + + ) + + + + cutBefore = true + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWhen.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWhen.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWhen.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bufferWhen ( + + + ) + + + + + + + , + + + + + + { + + + + { + + + + { + + + + { + + + + { + + + + { + + + + } + + + + } + + + + } + + + + } + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWhenWithSupplier.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWhenWithSupplier.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWhenWithSupplier.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bufferWhen ( + + + ) + + + + + + + , + + + + , + + + + + + () + + + + } + + + + } + + + + } + + + + } + + + + { + + + + { + + + + { + + + + { + + + + { + + + + { + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWhile.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWhile.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWhile.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bufferWhile ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithBoundary.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithBoundary.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithBoundary.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + buffer ( + + + ) + + + + + + + + + + + cancel() + + + + }{ + + + + }{ + + + + }{ + + + + }{ + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + buffer ( maxSize = 3 ) + + + + + + + + + + + + request(2 ) + + + + request(6 ) + + + + + + + + + + + + + + + + + + + + + + + Upstream requests = R * maxSize + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSizeEqualsSkipSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSizeEqualsSkipSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSizeEqualsSkipSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + buffer ( maxSize = 3 , skip = 3 ) + + + + + + + + + + + + request(2 ) + + + + request(6 ) + + + + + + + + + + + + + + + + + + + + + + + Upstream requests = R * maxSize + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSizeGreaterThanSkipSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSizeGreaterThanSkipSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSizeGreaterThanSkipSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + buffer (maxSize = 3, skip = 2 ) + + + + request(3 ) + + + + request(9 ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Upstream requests, first maxSize+skip*(R-1),then R*skip + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSizeLessThanSkipSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSizeLessThanSkipSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithMaxSizeLessThanSkipSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + buffer (maxSize = 2, skip = 3 ) + + + + request(3 ) + + + + request(9 ) + + + + + + + + + + + + + + + + + + + + Upstream requests = R *skip + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespan.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespan.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespan.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + buffer ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespanEqualsOpenBufferEvery.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespanEqualsOpenBufferEvery.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespanEqualsOpenBufferEvery.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + buffer ( + + + + + bufferingTimespan + + + openBufferEvery + + + , + + + = + + + = + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespanGreaterThanOpenBufferEvery.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespanGreaterThanOpenBufferEvery.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespanGreaterThanOpenBufferEvery.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + buffer ( + + + + + + + + + + + + + + + + + bufferingTimespan + + + openBufferEvery + + + , + + + = + + + = + + + ) + + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespanLessThanOpenBufferEvery.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespanLessThanOpenBufferEvery.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/bufferWithTimespanLessThanOpenBufferEvery.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + buffer ( + + + + + , + + + ) + + + + request(unbounded ) + + + bufferingTimespan + + + openBufferEvery + + + = + + + = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cache + + + + + + + + + + + + + + + request() + + + + request(unbounded ) + + + + request() + + + + + subscribe() + + + + subscribe() + + + + subscribe() + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cache + + + + + + + + + + + + + request() + + + + request(unbounded ) + + + + request() + + + + + + subscribe() + + + + subscribe() + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithHistoryLimitForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithHistoryLimitForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithHistoryLimitForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cache ( history = 2 ) + + + + + + + + + + + + + + + + request() + + + + request() + + + + request() + + + + + + subscribe() + + + + subscribe() + + + + subscribe() + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithTtlAndMaxLimitForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithTtlAndMaxLimitForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithTtlAndMaxLimitForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + request() + + + + subscribe() + + + + request(unbounded ) + + + + + + + + + + cache ( + + + ) + + + + 3, + + + + + + + + + + + + + + + + + + + subscribe() + + + + request() + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithTtlForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithTtlForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithTtlForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + + + + + + + + cache ( + + + ) + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithTtlForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithTtlForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cacheWithTtlForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + + + + cache ( + + + ) + + + + + + + + + subscribe() + + + + request() + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cancelOnForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cancelOnForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cancelOnForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,330 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancelOn ( + + + ) + + + + + + + + + + + + cancel() + + + cancel() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cancelOnForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cancelOnForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/cancelOnForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancelOn ( + + + ) + + + + + + + + + + + cancel() + + + cancel() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/castForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/castForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/castForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cast ( + + + ) + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/castForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/castForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/castForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cast ( + + + ) + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collect.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collect.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collect.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + collect ( + + + + + , + + + ) + + + ) + + + ( ) + + + , + + + ( + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectList.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectList.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectList.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + collectList + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMapWithKeyAndValueExtractors.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMapWithKeyAndValueExtractors.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMapWithKeyAndValueExtractors.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + + + + + + + + 1 + + + + + 2 + + + + + 3 + + + + + + + + + 4 + + + + + + 5 + + + + } + + + : + + + , + + + + request(unbounded) + + + k( + + + ) + + + + collectMap( + + + + k( + + + ) + + + + ) + + + + v( + + + ) + + + + , + + + + + v( + + + ) + + + 4 + + + + + : + + + , + + + k( + + + ) + + + + + v( + + + ) + + + 5 + + + + + : + + + k( + + + ) + + + + + v( + + + ) + + + 3 + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMapWithKeyExtractor.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMapWithKeyExtractor.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMapWithKeyExtractor.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + + + + + + + + 1 + + + + + 2 + + + + + 3 + + + + + + + 4 + + + + + 5 + + + + + 3 + + + + + + + + + + + 4 + + + + + + 5 + + + + } + + + : + + + , + + + : + + + , + + + : + + + collectMap( + + + + key( + + + ) + + + + ) + + + + request(unbounded) + + + key( + + + ) + + + key( + + + key( + + + ) + + + ) + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMultiMapWithKeyAndValueExtractors.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMultiMapWithKeyAndValueExtractors.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMultiMapWithKeyAndValueExtractors.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 2 + + + + + + + 3 + + + + + + + { + + + + } + + + + request(unbounded) + + + + collectMultiMap( + + + + key( + + + ) + + + + ) + + + + v( + + + ) + + + + , + + + + + : + + + , + + + k( + + + ) + + + + + v( + + + ) + + + 1 + + + + + : + + + k( + + + ) + + + + + v( + + + ) + + + 2 + + + ( + + + , + + + + + v( + + + ) + + + 3 + + + ) + + + ( + + + ) + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMultiMapWithKeyExtractor.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMultiMapWithKeyExtractor.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectMultiMapWithKeyExtractor.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + 2 + + + + + + + 3 + + + + + + + { + + + + + + 1 + + + + + 2 + + + } + + + , + + + ( + + + + + 3 + + + ) + + + ( + + + ) + + + , + + + + request(unbounded) + + + collectMultiMap( + + + + key( + + + ) + + + + ) + + + + + : + + + key( + + + ) + + + + + : + + + key( + + + ) + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectSortedList.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectSortedList.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectSortedList.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 + + + + + 2 + + + + + 5 + + + + + + + 1 + + + + + 2 + + + + + 5 + + + + + 1 + + + + + + 4 + + + + + + 3 + + + + + 4 + + + , + + + , + + + , + + + , + + + collectSortedList + + + + request(unbounded) + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectSortedListWithComparator.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectSortedListWithComparator.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectSortedListWithComparator.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 + + + + 2 + + + + 5 + + + + + + 1 + + + + + 4 + + + + + request(unbounded) + + + + + + + + + 1 + + + + 2 + + + + 5 + + + + 3 + + + + 4 + + + , + + + , + + + , + + + , + + + + + collectSortedList ( + + + ) + + + + small + + + + big + + + + , + + + ( + + + ) + + + + big + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectWithCollector.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectWithCollector.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/collectWithCollector.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + collect(Collector ) + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/combineLatest.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/combineLatest.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/combineLatest.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + B1 + + + + + + + + + C1 + + + D1 + + + D2 + + + + + + + + + + + + A + + + + + B + + + + + D + + + + + + + 1 + + + 2 + + + + + + C + + + + combineLatest ( + + + ) + + + + + + + , + + + ( + + + ) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatAsyncSources.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatAsyncSources.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatAsyncSources.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + concat ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + subscribe() + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatMap.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatMap.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatMap.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + concatMap( + + + ) + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatMapIterable.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatMapIterable.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatMapIterable.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + concatMap( + + + ) + + + + + + + + + + ...) + + + , + + + , + + + ( + + + + + ) + + + ( + + + , + + + + ) + + + ( + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatVarSources.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatVarSources.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatVarSources.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + concat + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatWithForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatWithForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatWithForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + concatWith + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatWithForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatWithForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatWithForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + concatWith + + + + + + + + + + + subscribe() + + + + subscribe() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatWithValues.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatWithValues.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/concatWithValues.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + concatWithValues ( + + + , + + + ) + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/count.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/count.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/count.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + count + + + + + 4 + + + + + + request(unbounded ) + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/createForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/createForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/createForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(1) + + + + + request(3) + + + + + + + + + + + + create + + + addListener() + + + + subscribe() + + + + cancel() + + + + + removeListener() + + + source + + + + + sink + + + + ... + + + multithreaded + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/createForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/createForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/createForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + create + + + + addListener() + + + + subscribe() + + + + + removeListener() + + + sink + + + + + + + + + + + + + + + source + + + multithreaded + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/createWithOverflowStrategy.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/createWithOverflowStrategy.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/createWithOverflowStrategy.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(1) + + + + + request(3) + + + + create(OverflowStrategy.LATEST) + + + + subscribe() + + + + cancel() + + + + + sink + + + + ... + + + ... + + + + + + + + + + + + + addListener() + + + + removeListener() + + + source + + + multithreaded + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/defaultIfEmpty.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/defaultIfEmpty.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/defaultIfEmpty.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + defaultIfEmpty ( + + + + ) + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/deferForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/deferForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/deferForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + defer ( + + + ) + + + + + + + ( ) + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/deferForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/deferForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/deferForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + defer ( + + + ) + + + + + ( ) + + + + + subscribe() + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delay.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delay.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delay.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + delay ( + + + ) + + + + + + + 0 + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayElement.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayElement.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayElement.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + delayElement ( + + + + ) + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayElements.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayElements.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayElements.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + delayElements ( + + + + ) + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySequence.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySequence.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySequence.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + delaySequence ( + + + + ) + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + delaySubscription ( + + + + ) + + + + subscribe() + + + + subscribe() + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + delaySubscription ( + + + + ) + + + + subscribe() + + + + subscribe() + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionWithPublisherForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionWithPublisherForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionWithPublisherForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + delaySubscription ( + + + ) + + + + + + + + + + subscribe() + + + + cancel() + + + ! + + + ! + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionWithPublisherForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionWithPublisherForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delaySubscriptionWithPublisherForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + delaySubscription ( + + + ) + + + + + + subscribe() + + + + cancel() + + + + + + ! + + + + + ! + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayUntilForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayUntilForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayUntilForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + delayUntil ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayUntilForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayUntilForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/delayUntilForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + delayUntil ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/dematerializeForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/dematerializeForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/dematerializeForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dematerialize + + + + + + + + + + + + + + + + onNext + + + + + + + + onNext + + + + + + + + onComplete + + + + + + + + + + onError + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/dematerializeForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/dematerializeForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/dematerializeForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dematerialize + + + + + + + + + + onComplete + + + + + + + + + + onError + + + + + + + + + + + + + + onNext + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinct.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinct.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinct.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + distinct + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinctUntilChanged.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinctUntilChanged.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinctUntilChanged.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + distinctUntilChanged + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinctUntilChangedWithKey.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinctUntilChangedWithKey.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinctUntilChangedWithKey.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + distinctUntilChanged ( + + + + + + + + + + + + + ) + + + ) + + + color( + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinctWithKey.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinctWithKey.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/distinctWithKey.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + distinct ( + + + ) + + + + + + + + shape( + + + + + + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterSuccessOrError.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterSuccessOrError.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterSuccessOrError.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doAfterSuccessOrError( + + + ) + + + + + + + + + , + + + ( + + + ) + + + + + + + + + + + + + + + + + + + + + + doAfterSuccessOrError( + + + ) + + + + + + , + + + ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterTerminateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterTerminateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterTerminateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doAfterTerminate ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doAfterTerminate ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterTerminateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterTerminateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doAfterTerminateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doAfterTerminate ( + + + + + + + + + + + + + + + + + + + + + ) + + + + + + + + + + + + doAfterTerminate ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFinallyForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFinallyForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFinallyForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doFinally(signalType + + + ) + + + + + + + + COMPLETE + + + + + + + + + + + + + + + ERROR + + + + + + + + + + + + + + + + + + + + + CANCEL + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + cancel() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFinallyForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFinallyForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFinallyForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doFinally(signalType + + + ) + + + + + + COMPLETE + + + + + + + ERROR + + + + + + + + + + + + + CANCEL + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + cancel() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFirstForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFirstForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFirstForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doFirst ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + () + + + + + + + + + subscribe() + + + subscribe() + + + onSubscribe() + + + + onSubscribe() + + + + request() + + + + request() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFirstForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFirstForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doFirstForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doFirst ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + () + + + + + + + + + subscribe() + + + subscribe() + + + onSubscribe() + + + + onSubscribe() + + + + request() + + + + request() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnCancelForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnCancelForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnCancelForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnCancel ( + + + ) + + + + + + + + cancel() + + + + + + + + + + cancel() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnCancelForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnCancelForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnCancelForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnCancel ( + + + ) + + + + + + cancel() + + + + cancel() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnComplete.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnComplete.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnComplete.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnComplete ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnEachForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnEachForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnEachForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnEach( + + + ) + + + Signal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnEachForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnEachForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnEachForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnEach( + + + ) + + + Signal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnError ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnError ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithClassPredicateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithClassPredicateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithClassPredicateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnError ( + + + ) + + + , + + + doOnError ( + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithClassPredicateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithClassPredicateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithClassPredicateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnError ( + + + ) + + + , + + + doOnError ( + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithPredicateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithPredicateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithPredicateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnError ( + + + ) + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnError ( + + + ) + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithPredicateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithPredicateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnErrorWithPredicateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnError ( + + + ) + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnError ( + + + ) + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnNextForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnNextForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnNextForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnNext ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnNextForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnNextForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnNextForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnNext ( + + + ) + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnRequestForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnRequestForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnRequestForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request() + + + + doOnRequest (r + + + ) + + + + + + + + + + + + + request() + + + + + + + + + + + + + + + + + + + + + r + + + r + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnRequestForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnRequestForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnRequestForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request() + + + + doOnRequest (r + + + ) + + + + + + + + + + request() + + + + + + + + + + + + + + + + + + + + + r + + + r + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSubscribe.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSubscribe.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSubscribe.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onSubscribe() + + + + + + onSubscribe() + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + + + + + doOnSubscribe (sub + + + ) + + + + + + + + + + + + + + + + + + + + + sub + + + + sub + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSuccess.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSuccess.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSuccess.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnSuccess ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSuccessOrError.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSuccessOrError.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnSuccessOrError.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnSuccessOrError(( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + ) + + + ... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnTerminateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnTerminateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnTerminateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnTerminate ( + + + ) + + + + doOnTerminate ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + () + + + + () + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnTerminateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnTerminateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/doOnTerminateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + doOnTerminate ( + + + ) + + + + doOnTerminate ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + () + + + + () + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elapsedForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elapsedForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elapsedForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + elapsed + + + + + + + + + + + t 1 = + + + + t 2 = + + + + onSubscribe() + + + + t 0 + + + + , + + + + t 1 + + + + , + + + + t 2 + + + + , + + + + + + t 0 = + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elapsedForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elapsedForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elapsedForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + elapsed + + + + + + t 0 = + + + + onSubscribe() + + + + + + t 0 + + + + , + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elementAt.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elementAt.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elementAt.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + elementAt ( 1 ) + + + + + + + + + + + + cancel() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elementAtWithDefault.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elementAtWithDefault.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/elementAtWithDefault.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + elementAt ( 1, + + + + ) + + + + + + + + + + + + + + + cancel() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/empty.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/empty.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/empty.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + empty + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/error.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/error.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/error.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + error ( + + + + + ) + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/errorWhenRequested.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/errorWhenRequested.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/errorWhenRequested.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + request() + + + + + + + + + subscribe() + + + error ( + + + + + error ( + + + + + ) + + + true + + + , + + + ) + + + false + + + , + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/errorWithSupplier.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/errorWithSupplier.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/errorWithSupplier.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + error ( + + + + + ) + + + ( ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + filter ( + + + ) + + + + + + + + isCircle( + + + + + + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + filter ( + + + ) + + + + + + + + isCircle( + + + + + + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterWhenForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterWhenForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterWhenForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + cancel() + + + + + + + + + subscribe() + + + + subscribe() + + + + + subscribe() + + + + filterWhen ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterWhenForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterWhenForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/filterWhenForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + subscribe() + + + + + + + + + + + subscribe() + + + + cancel() + + + + + + + + + + + + + + + + + + + + + filterWhen ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithSignalForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithSignalForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithSignalForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + + + firstWithSignal + + + + + + + + + + + + cancel() + + + + + + + + firstWithSignal + + + + + + + + + + + + subscribe() + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithSignalForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithSignalForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithSignalForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + subscribe() + + + + subscribe() + + + + + + + + + + firstWithSignal + + + + + + + + + + + + cancel() + + + + + + + + firstWithSignal + + + + + + + + + + + + subscribe() + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithValueForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithValueForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithValueForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + + + + + + + + + + + + + + + firstWithValue + + + + + + + + + + + + subscribe() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithValueForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithValueForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/firstWithValueForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + + + + + + + + + firstWithValue + + + + + + + + + + + + subscribe() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flatMap ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flatMap ( + + + ) + + + + + + + + + + + + + + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapIterableForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapIterableForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapIterableForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flatMapIterable ( + + + ) + + + + + + ...) + + + ( + + + , + + + , + + + ) + + + ( + + + + , + + + , + + + + + + + + + + + ) + + + ( + + + , + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapIterableForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapIterableForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapIterableForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flatMapIterable ( + + + + + + ( + + + , + + + , + + + ) + + + ( + + + + , + + + , + + + + + + + + + ...) + + + + ) + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapMany.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapMany.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapMany.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flatMapMany ( + + + ) + + + + + + + + + + + + + + + + + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapManyWithMappersOnTerminalEvents.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapManyWithMappersOnTerminalEvents.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapManyWithMappersOnTerminalEvents.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flatMapMany ( + + + ) + + + + + + + + + + + + + + + , + + + , + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapSequential.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapSequential.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapSequential.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,381 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + request() + + + flatMapSequential( + + + + + + + + ) + + + + + + + + subscribe() + + + + subscribe() + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapSequentialWithConcurrency.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapSequentialWithConcurrency.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapSequentialWithConcurrency.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,391 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + request(2) + + + flatMapSequential( + + + + + + + + maxConcurrency = 2 + + + ) + + + , + + + + + + + + subscribe() + + + + subscribe() + + + + + request(1 ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapSequentialWithConcurrencyAndPrefetch.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapSequentialWithConcurrencyAndPrefetch.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapSequentialWithConcurrencyAndPrefetch.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + request(2) + + + flatMapSequential( + + + + + + + + maxConcurrency = 2 + + + prefetch = 3 + + + ) + + + , + + + , + + + + + + + request(3 ) + + + + + subscribe() + + + request(3 ) + + + + + subscribe() + + + request(3 ) + + + + + + request(1 ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapWithConcurrency.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapWithConcurrency.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapWithConcurrency.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + request( 2 ) + + + flatMap ( + + + + + + + + maxConcurrency = 2 + + + ) + + + , + + + + + + + + subscribe() + + + + subscribe() + + + + + request(1 ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapWithConcurrencyAndPrefetch.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapWithConcurrencyAndPrefetch.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapWithConcurrencyAndPrefetch.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + flatMap ( + + + + + + + + + + + + + + + + + + + request( 2 ) + + + maxConcurrency = 2 + + + prefetch = 3 + + + ) + + + , + + + , + + + + + + + request(3 ) + + + + + subscribe() + + + request(3 ) + + + + + subscribe() + + + request(3 ) + + + + + + request(1 ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapWithMappersOnTerminalEventsForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapWithMappersOnTerminalEventsForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flatMapWithMappersOnTerminalEventsForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,352 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flatMap ( + + + ) + + + + + + + + + + + + + + + + , + + + , + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/flux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + operator (...) + + + + + + These are the items emitted by the Flux + + + + + + + + + + + + These dotted lines and this boxindicate that a transformationis being applied to the FluxThe text inside the box showsthe nature of the transformation + + + This vertical line indicates thatthe Flux has completed successfully + + + This is the timeline of the FluxTime flows from left to right + + + This Flux is the resultof the transformation + + + If for some reason the Flux terminatesabnormally, with an error, the verticalline is replaced by an X + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromArray.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromArray.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromArray.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ] + + + [ + + + fromArray + + + , + + + , + + + , + + + , + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromCallable.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromCallable.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromCallable.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fromCallable ( + + + ) + + + + + subscribe() + + + () + + + + { + + + + } + + + { + + + + } + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + from(Publisher) + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + from(Publisher) + + + + + + + + + + + + + + + cancel() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromFuture.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromFuture.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromFuture.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + + + + + + } + + + fromFuture ( + + + ) + + + { + + + } + + + + + + + + + + + subscribe() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromFutureSupplier.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromFutureSupplier.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromFutureSupplier.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,980 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + + + } + + + fromFuture ( + + + ) + + + ( ) + + + { + + + } + + + + + + + + + + + + subscribe() + + + + + + + + + + Supplier#get() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromIterable.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromIterable.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromIterable.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fromIterable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + + + { + + + , + + + , + + + , + + + , + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromRunnable.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromRunnable.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromRunnable.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fromRunnable ( + + + ) + + + () + + + + { } + + + + { + + + + subscribe() + + + } + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromStream.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromStream.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromStream.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fromStream + + + + + + + + + + + + + + } + + + { + + + , + + + , + + + , + + + , + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromSupplier.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromSupplier.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/fromSupplier.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fromSupplier ( + + + + ) + + + () + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/generate.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/generate.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/generate.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ( + + + , + + + generate ( + + + ) + + + ,sink) + + + sink.next ( + + + ); + + + return + + + ; + + + + request(1) + + + + + + request(1) + + + + + request(1) + + + + + + + 0 + + + + 1 + + + + 2 + + + () + + + + + + + v + + + + + + + + + + + + + + + + + + + V + + + + + + + + + + + 0 + + + + + + v + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/generateStateless.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/generateStateless.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/generateStateless.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(1) + + + + + + request(1) + + + + + request(1) + + + + + + 58 + + + + 59 + + + + 02 + + + + generate ( + + + ) + + + (sink) + + + sink.next (clock.seconds()) + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/generateWithCleanup.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/generateWithCleanup.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/generateWithCleanup.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ( + + + , + + + generate ( + + + ) + + + ,sink) + + + sink.next ( + + + ); + + + return + + + ; + + + + request(1) + + + + + + request(1) + + + + + request(1) + + + + + + + 0 + + + + 1 + + + + 2 + + + () + + + + , + + + + + + + + v + + + + + + + + + + + + + + + + + + + V + + + + + + + + + + + 0 + + + + + + v + + + + + + + + + + + v + + + + + + + + + + + v + + + + + + + + + + + + + + 2 + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/groupByWithKeyMapper.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/groupByWithKeyMapper.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/groupByWithKeyMapper.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + groupBy ( + + + ) + + + + + + + + shape( + + + + + + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/groupByWithKeyMapperAndValueMapper.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/groupByWithKeyMapperAndValueMapper.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/groupByWithKeyMapperAndValueMapper.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + groupBy ( + + + ) + + + + + + + + shape( + + + + + + + + ), + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + noBorder( + + + + + + + + ) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/groupJoin.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/groupJoin.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/groupJoin.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + groupJoin ( + + + + + 1 + + + + 2 + + + + 4 + + + + + 3 + + + A + + + B + + + + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1,A + + + + 3,B + + + + 4,C + + + + + + + + + 4,B + + + + , + + + + + , + + + ( + + + ) + + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + 2,A + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/hasElementForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/hasElementForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/hasElementForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hasElement ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/hasElementForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/hasElementForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/hasElementForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hasElement + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/hasElements.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/hasElements.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/hasElements.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hasElements + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ignoreElementForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ignoreElementForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ignoreElementForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + ignoreElement + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ignoreElementsForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ignoreElementsForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ignoreElementsForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ignoreElements + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ignoreElementsForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ignoreElementsForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ignoreElementsForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ignoreElements + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/index.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/index.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/index.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + index + + + + + + + + + + 0 + + + + , + + + + 1 + + + + , + + + + 2 + + + + , + + + 0 + + + 1 + + + 2 + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/indexWithMapper.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/indexWithMapper.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/indexWithMapper.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + , + + + + 2 + + + + + , + + + 3 + + + + , + + + + index( + + + ) + + + + , + + + ( + + + ) + + + + + i +1 + + + + , + + + i + + + + 0 + + + 1 + + + 2 + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/interval.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/interval.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/interval.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + interval ( + + + + + + 0 + + + + + 1 + + + + + 2 + + + + + + + + 4 + + + + + 3 + + + + + + + + + + + + + subscribe() + + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + ... + + + ... + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/intervalWithDelay.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/intervalWithDelay.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/intervalWithDelay.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + interval ( + + + + + + 0 + + + + + 1 + + + + + 2 + + + + + + + + 4 + + + + + 3 + + + + + + + + + + + subscribe() + + + + + period = + + + ) + + + delay = + + + + , + + + + + + + + + + + + + + + + + + + + + + + ... + + + ... + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/join.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/join.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/join.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + join ( + + + ( + + + ) + + + + , + + + + + 1 + + + + 2 + + + + 4 + + + + + 3 + + + + A + + + + B + + + + C + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1,A + + + + 3,B + + + + 4,C + + + + + + + + 4,B + + + + ) + + + + + + , + + + + + , + + + + + + + + + + + + + 2,A + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/just.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/just.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/just.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + just ( + + + ) + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/justMultiple.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/justMultiple.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/justMultiple.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + just ( + + + ) + + + , + + + , + + + , + + + , + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/justOrEmpty.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/justOrEmpty.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/justOrEmpty.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + justOrEmpty (null ) + + + + justOrEmpty ( + + + ) + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/last.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/last.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/last.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + last + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/lastWithDefault.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/lastWithDefault.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/lastWithDefault.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + last ( + + + + ) + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/limitRate.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/limitRate.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/limitRate.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + limitRate(2) + + + + + + + request(2) + + + + request(n) + + + + request(2) + + + + + + + + + + + + request(2) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/limitRateWithHighAndLowTide.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/limitRateWithHighAndLowTide.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/limitRateWithHighAndLowTide.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + limitRate(highTide = 10, lowTide = 2 ) + + + + + + + request (10) + + + + request(n) + + + + request(2) + + + + + + + + + + + + request(2) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/logForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/logForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/logForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + log + + + + + onSubscribe () + + + + request() + + + + + + + + request() + + + + + + + + + onSubscribe () + + + + + + + + + onSubscribe () + + + + + onSubscribe () + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + + cancel() + + + + + + + + + + onSubscribe () + + + + + onSubscribe () + + + + Logger + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/logForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/logForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/logForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onSubscribe () + + + + request() + + + + + + + request() + + + + + + + + onSubscribe () + + + + + cancel() + + + + + + + + + onSubscribe () + + + + + + + cancel() + + + onSubscribe () + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onSubscribe () + + + + + onSubscribe () + + + log + + + + + Logger + + + log + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + map ( + + + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + map ( + + + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapNotNullForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapNotNullForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapNotNullForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mapNotNull ( + + + + + ) + + + + + ? null : + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapNotNullForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapNotNullForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mapNotNullForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mapNotNull ( + + + + + ) + + + + + + + + + + + + + + mapNotNull( + + + + + ) + + + + + ?null: + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/materializeForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/materializeForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/materializeForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + materialize + + + + + + + + + + + + + + + + + + + + + + onNext + + + + + + + + onNext + + + + + onComplete + + + + onError + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/materializeForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/materializeForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/materializeForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + materialize + + + + + + + + + + + + + + + + + + + + + onNext + + + + + + + + onComplete + + + + + onError + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeAsyncSources.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeAsyncSources.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeAsyncSources.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + merge( + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + subscribe() + + + + ) + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparing.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparing.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparing.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + 3 + 2 + 4 + 1 + + 5 + 3 + 2 + 4 + 1 + + 0 + + + 0 + + mergeComparing( + + small + + big + + , + ( + ) + + ) + big + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingNaturalOrder.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingNaturalOrder.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingNaturalOrder.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 3 + 5 + 2 + 4 + + 1 + 3 + 5 + 2 + 4 + + 0 + + + 0 + + mergeComparing + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingWith.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingWith.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeComparingWith.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + 3 + 2 + 4 + 1 + + 5 + 3 + 2 + 4 + 1 + + 0 + + + 0 + + + + + mergeComparingWith( + small + big + , + ( + ) + + ) + big + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeFixedSources.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeFixedSources.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeFixedSources.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + merge + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeSequentialAsyncSources.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeSequentialAsyncSources.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeSequentialAsyncSources.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + + subscribe() + + + + + mergeSequential( + + + ) + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeSequentialVarSources.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeSequentialVarSources.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeSequentialVarSources.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mergeSequential + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeWithForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeWithForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeWithForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mergeWith + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeWithForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeWithForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mergeWithForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mergeWith + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/mono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + operator (... ) + + + + This is the optional item + + + emitted by the Mono + + + + + + These dotted lines and this boxindicate that a transformationis being applied to the MonoThe text inside the box showsthe nature of the transformation + + + This vertical line indicates thatthe Mono has completed successfully + + + This is the timeline of the MonoTime flows from left to right + + + This Mono is the resultof the transformation + + + If for some reason the Mono terminatesabnormally, with an error, the verticalline is replaced by an X + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/never.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/never.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/never.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + never + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/next.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/next.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/next.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + next + + + + + + + + + + + + + cancel() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ofTypeForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ofTypeForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ofTypeForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ofType ( + + + ) + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ofTypeForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ofTypeForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/ofTypeForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ofType ( + + + ) + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBuffer.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBuffer.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBuffer.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onBackpressureBuffer + + + + + + + + + + + + request(2) + + + + request(1) + + + + request(unbounded ) + + + + + request(1) + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithDurationAndMaxSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithDurationAndMaxSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithDurationAndMaxSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , maxSize = 2 ) + + + + + + + + request(1) + + + + request(2) + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + onBackpressureBuffer ( + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithMaxSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithMaxSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithMaxSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onBackpressureBuffer ( maxSize = 2 ) + + + + + + + + request(1) + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + cancel() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithMaxSizeConsumer.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithMaxSizeConsumer.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithMaxSizeConsumer.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(1) + + + + request(2) + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onBackpressureBuffer ( maxSize = 2, + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithMaxSizeStrategyDropOldest.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithMaxSizeStrategyDropOldest.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureBufferWithMaxSizeStrategyDropOldest.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onBackpressureBuffer ( maxSize = 2, DROP_OLDEST ) + + + + + + + + request(1) + + + + request(2) + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureDrop.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureDrop.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureDrop.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onBackpressureDrop + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(2) + + + + request(unbounded ) + + + + request(2) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureDropWithConsumer.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureDropWithConsumer.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureDropWithConsumer.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(2) + + + + request(unbounded ) + + + + request(2) + + + onBackpressureDrop ( + + + ) + + + + + + + + { } + + + + { + + + } + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureError.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureError.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureError.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onBackpressureError + + + + + + + + + + + + + + + + + + + + + + + request(2) + + + + request(unbounded ) + + + + cancel() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureLatest.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureLatest.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onBackpressureLatest.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(2) + + + + request(2) + + + + request(unbounded ) + + + + onBackpressureLatest + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorContinue.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorContinue.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorContinue.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + map(this::throwIfYellow ) + + + + + + + onErrorContinue(( + + + + + , + + + ) + + + + { + + + } + + + + { + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + ) + + + influences upstream + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorContinueWithClassPredicate.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorContinueWithClassPredicate.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorContinueWithClassPredicate.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,603 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + map(this::throwIfNotGreen ) + + + + + + onErrorContinue( + + + + + , + + + ) + + + + { + + + } + + + + { + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + , ( + + + ) + + + + influences upstream + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorContinueWithPredicate.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorContinueWithPredicate.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorContinueWithPredicate.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,371 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + map(Util::throwIfNotGreen ) + + + + + + onErrorContinue(Exceptions::isRed, ( + + + + + , + + + ) + + + + { + + + } + + + + { + + + } + + + + + + + + + + + + + + + + + + ) + + + + + + + + influences upstream + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorMap ( + + + ) + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorMap ( + + + ) + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithClassPredicateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithClassPredicateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithClassPredicateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorMap ( + + + ) + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithClassPredicateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithClassPredicateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithClassPredicateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorMap ( + + + ) + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithPredicateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithPredicateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithPredicateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorMap ( + + + ) + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithPredicateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithPredicateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorMapWithPredicateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorMap ( + + + ) + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorResumeForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorResumeForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorResumeForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorResume ( + + + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorResumeForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorResumeForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorResumeForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorResume ( + + + + + ) + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorReturnForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorReturnForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorReturnForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorReturn ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorReturnForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorReturnForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/onErrorReturnForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onErrorReturn ( + + + ) + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/orForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/orForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/orForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or + + + + + + + + + + + + + + + + + + + cancel() + + + + subscribe() + + + + subscribe() + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/orForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/orForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/orForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + or + + + + + + + + + + + + + cancel() + + + + subscribe() + + + + subscribe() + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/parallel.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/parallel.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/parallel.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + parallel ( parallelism = 2 ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publish.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publish.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publish.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + publish ( prefetch = 32 ) + + + + + + + + + connect() + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + request( 32 ) + + + + + + subscribe() + + + + subscribe() + + + + subscribe() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publishNext.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publishNext.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publishNext.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + publishNext + + + + + + + + + + + + + + cancel() + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publishOnForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publishOnForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publishOnForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + publishOn ( + + + ) + + + + + + + + + + subscribe() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publishOnForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publishOnForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/publishOnForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + publishOn ( + + + + + + + + + + + + + ) + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/push.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/push.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/push.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(1) + + + + + request(3) + + + + + + + + push + + + + addListener() + + + + subscribe() + + + + cancel() + + + + + + removeListener() + + + source + + + + + sink + + + ... + + + singlethreaded + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/pushWithOverflowStrategy.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/pushWithOverflowStrategy.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/pushWithOverflowStrategy.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(1) + + + + + request(3) + + + + + + + + push(OverflowStrategy.LATEST) + + + addListener() + + + + subscribe() + + + + cancel() + + + + + removeListener() + + + source + + + sink + + + + ... + + + singlethreaded + + + + + + ... + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/range.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/range.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/range.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + range ( n, m ) + + + + + + + + n+1 + + + + n + + + n+2 + + + + + n+m-1 + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/reduce.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/reduce.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/reduce.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + , + + + ) + + + ( + + + + + + + , + + + + reduce ( + + + ) + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/reduceWith.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/reduceWith.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/reduceWith.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + , + + + ) + + + ( + + + + + + + , + + + + reduceWith (() + + + ) + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/reduceWithSameReturnType.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/reduceWithSameReturnType.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/reduceWithSameReturnType.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + , + + + ) + + + ( + + + + + reduce ( + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/refCount.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/refCount.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/refCount.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + refCount + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + subscribe() + + + + subscribe() + + + subscribe() + + + + cancel() + + + cancel() + + + + + + cancel() + + + + + + + cancel() + + + cancel() + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + connect + + + disconnect + + + + + connect + + + + disconnect + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/refCountWithMinSubscribers.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/refCountWithMinSubscribers.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/refCountWithMinSubscribers.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + refCount(minSubscribers=2 ) + + + + + + + + + + + + connect + + + + + + + subscribe() + + + subscribe() + + + subscribe() + + + + cancel() + + + + + + + + + cancel() + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + cancel() + + + + + + + disconnect + + + cancel() + + + + + + + subscribe() + + + connect + + + + + subscribe() + + + disconnect + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/refCountWithMinSubscribersAndGracePeriod.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/refCountWithMinSubscribersAndGracePeriod.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/refCountWithMinSubscribersAndGracePeriod.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,558 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + subscribe() + + + + cancel() + + + + + + + + + + + + + + + + + connect + + + + + + + + + + + + + + + disconnect + + + + refCount(minSubscribers=1, + + + + ) + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + repeat + + + + + + + + + subscribe() + + + + + + + + + + + subscribe() + + + + + + + + + + + subscribe() + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + repeat + + + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWhenEmpty.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWhenEmpty.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWhenEmpty.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,549 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + subscribe() + + + + + + + + + + + + + ! + + + + + + + + + + ! + + + + ! + + + + ! + + + + repeatWhenEmpty ( + + + ) + + + + + + + + + + + ! + + + + ! + + + + subscribe() + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWhenForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWhenForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWhenForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,450 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + subscribe() + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + 2 + + + + repeatWhen( + + + ) + + + + + + + + + + + n + + + + n + + + !=3 + + + subscribe() + + + + + + + + + + + + + 1 + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + + ! + + + + ! + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWhenForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWhenForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWhenForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + subscribe() + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + 1 + + + + + repeatWhen( + + + ) + + + + + + + + + + + n + + + + n + + + !=0 + + + + subscribe() + + + + + + + + + 1 + + + + + + 0 + + + + + + + + + + + + ! + + + + ! + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsAndPredicateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsAndPredicateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsAndPredicateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,599 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + 1 + + + 2 + + + 1 + + + 2 + + + repeat(1, () + + + ) + + + + repeat(4, () + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsAndPredicateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsAndPredicateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsAndPredicateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,763 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + + + + + + + 1 + + + 2 + + + 1 + + + 2 + + + repeat(1, () + + + ) + + + + repeat(4 , () + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,406 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + repeat(1 ) + + + + + + + + + subscribe() + + + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithAttemptsForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + repeat(2 ) + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithPredicateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithPredicateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithPredicateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + repeat(() + + + ) + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithPredicateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithPredicateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/repeatWithPredicateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + repeat(() + + + ) + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replay.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replay.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replay.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + replay + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + request(unbounded ) + + + + + + + subscribe() + + + + + + + + + + connect() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replayWithHistory.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replayWithHistory.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replayWithHistory.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + subscribe() + + + + + + + + + + replay (2 ) + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + subscribe() + + + + + + + + + connect() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replayWithHistoryAndTtl.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replayWithHistoryAndTtl.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replayWithHistoryAndTtl.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + replay ( + + + ) + + + + 2 , + + + + + + + + + + + request(unbounded ) + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + connect() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replayWithTtl.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replayWithTtl.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/replayWithTtl.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + subscribe() + + + + + + + + + + + replay ( + + + ) + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + + + connect() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryBackoffForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryBackoffForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryBackoffForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ? + + + + + + ? + + + retryBackoff(3, + + + + + + + + ) + + + + + + + + jitter + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryBackoffForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryBackoffForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryBackoffForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ? + + + + + + ? + + + retryBackoff(3, + + + + + + + + ) + + + + + + + + + + jitter + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + retry + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + retry + + + + + subscribe() + + + + + subscribe() + + + + + + + subscribe() + + + + + subscribe() + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,533 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + + retryWhen( + + + ) + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + + + ! + + + + ! + + + + ! + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,484 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + retryWhen( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + + + ! + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenSpecForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenSpecForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenSpecForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + retryWhen(spec) + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + + + ! + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + 1 + + + + + + + + + + + 2 + + + + + + Retry.backoff(2,Duration.ofMillis(100)) + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + + + ! + + + + 0 + + + + 1 + + + + 2 + + + spec = + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenSpecForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenSpecForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWhenSpecForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + retryWhen(spec) + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + + + ! + + + + + + + + + + + Retry.backoff(2,Duration.ofMillis(100)) + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + + + ! + + + + 0 + + + + 1 + + + + 2 + + + spec = + + + + + + + + + + + + + + + 0 + + + + + + + + + + + 1 + + + + + + + + + + + 2 + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsAndPredicateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsAndPredicateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsAndPredicateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,723 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + 1 + + + 2 + + + 1 + + + 2 + + + retry(1, () + + + ) + + + + retry(4 , () + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsAndPredicateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsAndPredicateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsAndPredicateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,803 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + subscribe() + + + + + subscribe() + + + + + subscribe() + + + + + + + + + + + + + + + subscribe() + + + + + subscribe() + + + + + subscribe() + + + + + + + 1 + + + 2 + + + 1 + + + 2 + + + retry(1, () + + + ) + + + + retry(4, () + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + retry(1 ) + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithAttemptsForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,391 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + retry(2 ) + + + + + + + subscribe() + + + + + + + subscribe() + + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithPredicateForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithPredicateForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithPredicateForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,465 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + retry(() + + + ) + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithPredicateForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithPredicateForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/retryWithPredicateForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + subscribe() + + + + + + + + + + + retry(() + + + ) + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleAtRegularInterval.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleAtRegularInterval.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleAtRegularInterval.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + + + + + + + sample ( + + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleFirstAtRegularInterval.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleFirstAtRegularInterval.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleFirstAtRegularInterval.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,273 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + + + + + + + + sampleFirst ( + + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleFirstWithSamplerFactory.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleFirstWithSamplerFactory.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleFirstWithSamplerFactory.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,514 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + cancel() + + + + + + + + + + + + + + ! + + + + ! + + + + sampleFirst ( + + + + + + ) + + + + + ! + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleTimeoutWithThrottlerFactory.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleTimeoutWithThrottlerFactory.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleTimeoutWithThrottlerFactory.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + cancel() + + + + + + + + + sampleTimeout ( + + + + + + ) + + + + + ! + + + + + + + ! + + + + + + + + + + cancel() + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleTimeoutWithThrottlerFactoryAndMaxConcurrency.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleTimeoutWithThrottlerFactoryAndMaxConcurrency.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleTimeoutWithThrottlerFactoryAndMaxConcurrency.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + cancel() + + + + + + + + + sampleTimeout ( + + + + + + ) + + + + + ! + + + + + + + ! + + + + + + + + + + cancel() + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleWithSampler.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleWithSampler.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sampleWithSampler.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + request() + + + + + + request() + + + + + cancel() + + + + + sample ( + + + ) + + + + + + + + + ! + + + + ! + + + + ! + + + + ! + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/scan.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/scan.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/scan.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + , + + + ) + + + ( + + + + + + + , + + + + scan ( + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/scanWith.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/scanWith.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/scanWith.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + , + + + ) + + + ( + + + + + + + , + + + + scanWith(() + + + ) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/scanWithSameReturnType.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/scanWithSameReturnType.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/scanWithSameReturnType.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + , + + + ) + + + ( + + + + + scan ( + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sequenceEqual.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sequenceEqual.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sequenceEqual.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sequenceEqual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/shareForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/shareForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/shareForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + share + + + + + + + + + + + + + + cancel() + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/shareNext.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/shareNext.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/shareNext.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + shareNext + + + + + + + + + + + + + + cancel() + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + subscribe() + + + + request() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + single + + + + + + + + + + + + + + cancel() + + + + + + request(unbounded ) + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + single + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleOrEmpty.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleOrEmpty.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleOrEmpty.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + singleOrEmpty + + + + + + + + + + + + + + + + + + cancel() + + + + request(unbounded ) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleWithDefault.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleWithDefault.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/singleWithDefault.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + single ( + + + + ) + + + + + + + + + + + + + + + + + + + + + cancel() + + + + request(unbounded ) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkDirectAllOrNothing.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkDirectAllOrNothing.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkDirectAllOrNothing.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sink + + + + + subscribe + + + + request(1) + + + .asFlux() + + + + + + + + + + + + + + request(1) + + + + + + + request(5) + + + + + + + + + + + subscribe + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkDirectBestEffort.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkDirectBestEffort.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkDirectBestEffort.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sink + + + + + subscribe + + + + request(1) + + + .asFlux() + + + + + + + + + + + + + + request(1) + + + + + + + request(5) + + + + + + + + + + + subscribe + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkNoWarmup.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkNoWarmup.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkNoWarmup.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,710 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sink + + + + + + + + subscribe + + subscribe + .asFlux() + + + + + + + + + + + warmup + hot + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkWarmup.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkWarmup.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sinkWarmup.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,782 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sink + + + + + + + + + + + + subscribe + + subscribe + .asFlux() + + + + + + + + + + + warmup + hot + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skip.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skip.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skip.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + skip (2 ) + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + request(2) + + + + + + + + + + + + request(n) + + + + request(n) + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipLast.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipLast.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipLast.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + skipLast (2 ) + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + request(2) + + + + request(n) + + + + request(n) + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipUntil.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipUntil.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipUntil.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + skipUntil ( + + + + ) + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipUntilOther.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipUntilOther.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipUntilOther.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + skipUntilOther ( + + + ) + + + + + cancel() + + + + + + + + + + + request(unbounded ) + + + + + + + + + + + + + + + + + + + + + + + + ! + + + + ! + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipWhile.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipWhile.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipWhile.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + skipWhile ( + + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipWithTimespan.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipWithTimespan.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/skipWithTimespan.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + skip ( + + + ) + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sort.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sort.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/sort.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + sort + + + + + + + + + 1 + + + 4 + + + 5 + + + 3 + + + 2 + + + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/startWithIterable.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/startWithIterable.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/startWithIterable.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) + + + ( + + + , + + + startWith + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/startWithPublisher.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/startWithPublisher.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/startWithPublisher.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + startWith + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/startWithValues.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/startWithValues.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/startWithValues.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ] + + + [ + + + , + + + startWith + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,331 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onSubscribe() + + + + subscribe() + + + + + subscribe ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + () + + + + + + + + + + + + + + + + + + + + + + , + + + , + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + onSubscribe() + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + onSubscribe() + + + + subscribe() + + + + subscribe ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + () + + + + + + + + + + + + + + + + + + + + + + , + + + , + + + , + + + + + + + + + + + + + + + + + + + + + + + + + onSubscribe() + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeIgoringAllSignalsForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeIgoringAllSignalsForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeIgoringAllSignalsForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe + + + + subscribe() + + + + subscribe() + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeIgoringAllSignalsForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeIgoringAllSignalsForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeIgoringAllSignalsForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe + + + + subscribe() + + + + subscribe() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeOnForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeOnForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeOnForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,301 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribeOn ( + + + ) + + + + + + + + + + subscribe() + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeOnForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeOnForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeOnForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,437 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribeOn ( + + + ) + + + + + + + + + + subscribe() + + + + + subscribe() + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorAndOnCompleteForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorAndOnCompleteForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorAndOnCompleteForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,375 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + subscribe ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + () + + + + + + + + + + + + + + + + + + + + , + + + , + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorAndOnCompleteForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorAndOnCompleteForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorAndOnCompleteForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + ) + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + () + + + + + + + + + + + + + + + + + + + + , + + + , + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + subscribe ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextAndOnErrorForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + ) + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + subscribe ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/subscribeWithOnNextForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + ) + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + subscribe ( + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchIfEmptyForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchIfEmptyForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchIfEmptyForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + switchIfEmpty + + + + + + subscribe() + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchIfEmptyForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchIfEmptyForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchIfEmptyForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + switchIfEmpty + + + + + + subscribe() + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchMap.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchMap.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchMap.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + switchMap( + + + ) + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + + + + + + cancel() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchOnFirst.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchOnFirst.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchOnFirst.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(1) + + + + + + + + + + + + + + + + + + + + request(9) + + + + request(10) + + + + switchOnFirst( + + + flux.filter(not( + + + .color()))) + + + + , + + + ( + + + ) + + + + flux + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + filter(not( + + + )) + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchOnNext.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchOnNext.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/switchOnNext.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + switchOnNext + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + subscribe() + + + + + cancel() + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/take.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/take.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/take.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + take ( 3 ) + + + + + + + + + + + + + + cancel() + + + + take (0 ) + + + + + + + + + cancel() + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLast.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLast.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLast.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + takeLast (2 ) + + + + + + + + + + + + + + + + request(unbounded ) + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestFalse.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestFalse.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestFalse.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + take(3, false) + + + + + + + + + + + + + + cancel() + + + + take(0, false) + + + + + + + + + cancel() + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestTrue.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestTrue.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeLimitRequestTrue.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + take(3, true) + + + + + + + request(23) + + + + request(2) + + + + request(2) + + + + request(1) + + + + + cancel() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeUntil.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeUntil.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeUntil.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + takeUntil( + + + ) + + + + + cancel() + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeUntilOtherForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeUntilOtherForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeUntilOtherForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + request(unbounded ) + + + + takeUntilOther( + + + ) + + + + + cancel() + + + + + + + + cancel() + + + + + + + + + request(unbounded ) + + + + + + cancel() + + + + + + + + + + ! + + + + ! + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeUntilOtherForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeUntilOtherForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeUntilOtherForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + takeUntilOther( + + + ) + + + + + cancel() + + + + + + + + request(unbounded ) + + + + + + cancel() + + + + + + + ! + + + + ! + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeWhile.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeWhile.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeWhile.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + takeWhile( + + + ) + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeWithTimespanForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeWithTimespanForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeWithTimespanForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + take ( + + + ) + + + + + subscribe() + + + + cancel() + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeWithTimespanForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeWithTimespanForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/takeWithTimespanForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + take ( + + + ) + + + + subscribe() + + + + + + + + + + + + + + + + + subscribe() + + + + + subscribe() + + + + + + + subscribe() + + + + + + + + + cancel() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenEmptyForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenEmptyForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenEmptyForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thenEmpty ( + + + ) + + + + + + + + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenEmptyForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenEmptyForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenEmptyForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thenEmpty ( + + + ) + + + + + + + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + then + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + then + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenManyForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenManyForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenManyForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thenMany ( + + + ) + + + + + + + + + + + + + + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenManyForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenManyForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenManyForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thenMany ( + + + ) + + + + + + + + + + + + + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenReturn.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenReturn.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenReturn.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thenReturn ( + + + ) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenWithMonoForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenWithMonoForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenWithMonoForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + then ( + + + ) + + + + + + + + + + + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenWithMonoForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenWithMonoForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/thenWithMonoForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + then ( + + + ) + + + + + + + + + + + + + + subscribe() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timedForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timedForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timedForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + timed + + + + t0 + + + 0 + + + + + + + + + + + + + onSubscribe() + + + tsub + + + + + + + + + + + + t0 + + + + , + + + + t0 + + + -tsub + + + + + t0 + + + -tsub + + + + + + + + + + + + + + t1 + + + t2 + + + + + + + + + + + + + + t1 + + + + , + + + + t1 + + + -tsub + + + + + t1 + + + -t0 + + + + + + + + + + + + + + + + + t2 + + + + , + + + + t2 + + + -tsub + + + + + t2 + + + -t1 + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timedForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timedForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timedForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + timed + + + + t0 + + + 0 + + + + + + + + + + + + + + + + + + + + onSubscribe() + + + tsub + + + + + t0 + + + , + + + + t0 + + + -tsub + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutFallbackForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutFallbackForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutFallbackForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + cancel() + + + + + subscribe() + + + + + + + + + + + + subscribe() + + + + timeout ( + + + ) + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutFallbackForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutFallbackForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutFallbackForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + + + cancel() + + + + timeout ( + + + ) + + + , + + + + + + + + subscribe() + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + cancel() + + + + + + + + + + + + subscribe() + + + + timeout ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + cancel() + + + + timeout ( + + + ) + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisher.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisher.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisher.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + timeout ( + + + ) + + + + + + cancel() + + + + + ! + + + + + ! + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisherAndFallbackForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisherAndFallbackForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisherAndFallbackForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,533 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + timeout ( + + + ) + + + + + , + + + + + + cancel() + + + + + ! + + + + ! + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisherFunctionAndFallbackForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisherFunctionAndFallbackForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisherFunctionAndFallbackForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + timeout ( + + + + ) + + + , + + + + + + cancel() + + + + + + + subscribe() + + + + + + + ! + + + + + + + ! + + + + + + + + ! + + + + + + + + + + + + + + + + + + + + + , + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisherFunctionForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisherFunctionForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timeoutPublisherFunctionForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + timeout ( + + + + ) + + + , + + + + + + cancel() + + + + + + + + subscribe() + + + + + + + ! + + + + + + + ! + + + + + + + + ! + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timestampForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timestampForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timestampForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + timestamp + + + + t 0 + + + t 1 + + + t 2 + + + 0 + + + + + + + + + + + + + + t 0 + + + + , + + + + t 1 + + + + , + + + + t 2 + + + + , + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timestampForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timestampForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/timestampForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + timestamp + + + + t 0 + + + 0 + + + + + + + + + + t 0 + + + + , + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toFuture.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toFuture.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toFuture.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + subscribe() + + + + toFuture + + + + { + + + } + + + + + + + subscribe() + + + {null} + + + + + + + complete(null) + + + + completeExceptionally(e) + + + + complete(v) + + + + subscribe() + + + { + + + + + + + + + + + + + + + + } + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toIterable.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toIterable.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toIterable.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,347 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + toIterable + + + + + + + + request() + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + iterator() + + + hasNext() + + + + + + + hasNext() + + + hasNext() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toIterableWithBatchSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toIterableWithBatchSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toIterableWithBatchSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + toIterable(2 ) + + + + + + + + request(2) + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + request(2) + + + + iterator() + + + hasNext() + + + + + + + + hasNext() + + + hasNext() + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toStream.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toStream.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toStream.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + toStream + + + + + + + + request() + + + + subscribe() + + + + + + + (from Iterator) + + + stream() + + + consumption + + + Stream + + + + + + + + + + + + + + + + + + hasNext() + + + + + + hasNext() + + + + + + + hasNext() + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toStreamWithBatchSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toStreamWithBatchSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/toStreamWithBatchSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + toStream(2 ) + + + + + + + + request(2) + + + + subscribe() + + + + + + + request(2) + + + + (from Iterator) + + + stream() + + + consumption + + + Stream + + + + + + + + + + + + + + + + + + hasNext() + + + + + + hasNext() + + + + + + + hasNext() + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformDeferredForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformDeferredForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformDeferredForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,614 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + take( + + + ) + + + external shared state + + + + subscribe() + + + + + + + + + + + + + + + + + take( + + + ) + + + + + + + v + + + + + + + + + transformDeferred( + + + + flux + + + flux.take( + + + ) + + + ) + + + + + v + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + 2 + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformDeferredForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformDeferredForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformDeferredForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,666 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + external shared state + + + + subscribe() + + + + + + + ) + + + map(x→x* + + + + + + map(x→x* + + + ) + + + + + + + v + + + + + + + + + transformDeferred( + + + + mono + + + mono.map(x→ x* + + + ) + + + ) + + + + + v + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + 2 + + + + 3 + + + 6 + + + 3 + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,3104 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + transform( + + + + flux + + + flux.take +( + + + + ) + + + ) + + + + take( + + + ) + + + external shared state + + + + subscribe() + + + + + + + + + + + + + + + + + + v + + + + + + + + + v + + + + + + + + + + + + + + + + 1 + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/transformForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,2647 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + transform( + + + + mono + + + mono.repeat + ( + + + ) + + + ) + + + + repeat( + + + ) + + + external shared state + + + + subscribe() + + + + + + + + + + v + + + + + + + + + v + + + + + + + + + + + + + + + + 1 + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + resource + + + + + + + + + + + subscribe() + + + + + subscribe() + + + + + + + resource + + + + + + + + + + + + + + + using( + + + + () + + + + + + + + + + + + + + + + + + + + + , + + + , + + + ) + + + + close + + + get() + + + + + close + + + get() + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + resource + + + + + + + + + subscribe() + + + + + subscribe() + + + + + resource + + + + + + + + + + + + close + + + close + + + + + using( + + + + () + + + + + + + + + + + + + + + + + + + + , + + + , + + + ) + + + + + get() + + + get() + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenCleanupErrorForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenCleanupErrorForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenCleanupErrorForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,479 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + + resource + + + + + + + usingWhen( + + + , + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + subscribe() + + + + + + + commitThrowing() + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenEarlyCancelForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenEarlyCancelForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenEarlyCancelForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,322 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + cancel() + + + resource + + + + usingWhen( + + + , + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + cancel() + + + + no resource created + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenFailureForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenFailureForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenFailureForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + subscribe() + + + + + + + + + + + + + usingWhen( + + + , + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + subscribe() + + + + + + + + resource + + + + + + + + + + + + + + + + + + + rollback( + + + + + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenFailureForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenFailureForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenFailureForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,372 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + subscribe() + + + + + + + + + + resource + + + + + + + usingWhen( + + + , + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + subscribe() + + + + + + + rollback( + + + + + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenSuccessForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenSuccessForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenSuccessForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + + + + usingWhen( + + + , + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + + + + + + + resource + + + + + + + + + + + + commit() + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenSuccessForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenSuccessForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/usingWhenSuccessForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,357 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + subscribe() + + + + + + + + + + resource + + + + + + + usingWhen( + + + , + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + + commit() + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/when.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/when.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/when.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cancel() + + + when + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/whenDelayError.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/whenDelayError.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/whenDelayError.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + whenDelayError + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowTimeout.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowTimeout.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowTimeout.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + windowTimeout ( + + + maxSize = 2 + + + , + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntil.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntil.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntil.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,616 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + windowUntil( + + + + ) + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntilChanged.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntilChanged.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntilChanged.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,641 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + windowUntilChanged + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntilChangedWithKeySelector.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntilChangedWithKeySelector.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntilChangedWithKeySelector.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + windowUntilChanged( + + + + + + + + + + + + + + + + + + ) + + + color( + + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntilWithCutBefore.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntilWithCutBefore.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowUntilWithCutBefore.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,668 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + windowUntil( + + + + + + + + cutBefore =true + + + , + + + windowUntil( + + + ) + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWhen.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWhen.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWhen.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,489 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + windowWhen ( + + + ) + + + + + + + + + + + + + + , + + + + + { + + + + { + + + + { + + + + } + + + + { + + + + { + + + + { + + + + } + + + + } + + + + } + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWhile.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWhile.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWhile.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + windowWhile( + + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithBoundary.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithBoundary.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithBoundary.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + window ( + + + ) + + + + + + + + + + + + + + + + + }{ + + + + }{ + + + + }{ + + + + }{ + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + window ( maxSize = 3 ) + + + + + + + + + + + + + + + + + request(2 ) + + + + request(6 ) + + + + + + + + + + + + + + + + + + + + + + + + Upstream requests = R * maxSize + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSizeEqualsSkipSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSizeEqualsSkipSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSizeEqualsSkipSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + window ( maxSize = 3, skip = 3 ) + + + + + + + + + + + + request(2 ) + + + + request(6 ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Upstream requests = R * maxSize + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSizeGreaterThanSkipSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSizeGreaterThanSkipSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSizeGreaterThanSkipSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + window ( maxSize = 3, skip = 2 ) + + + + + + + + + + + + request(3 ) + + + + request(7 ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Upstream requests, first maxSize+skip*(R-1),then R*skip + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSizeLessThanSkipSize.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSizeLessThanSkipSize.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithMaxSizeLessThanSkipSize.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + window ( maxSize =2 , skip = 3 ) + + + + + + + + + + + + + + + + + request(3 ) + + + + request(9 ) + + + + + + + + + + + + + + + + + + + + + + skip + + + maxSize + + + maxSize + + + Upstream requests = R *skip + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespan.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespan.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespan.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + window ( + + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespanEqualsOpenWindowEvery.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespanEqualsOpenWindowEvery.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespanEqualsOpenWindowEvery.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + window ( + + + + + , + + + ) + + + windowingTimespan= + + + openWindowEvery= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespanGreaterThanOpenWindowEvery.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespanGreaterThanOpenWindowEvery.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespanGreaterThanOpenWindowEvery.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,427 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + window ( + + + + , + + + ) + + + windowingTimespan= + + + openWindowEvery= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespanLessThanOpenWindowEvery.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespanLessThanOpenWindowEvery.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/windowWithTimespanLessThanOpenWindowEvery.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + window ( + + + + + , + + + ) + + + windowingTimespan= + + + openWindowEvery= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/withLatestFrom.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/withLatestFrom.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/withLatestFrom.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + B + + + + + D + + + + + + + + + + + + + + + 1 + + + + C1 + + + D2 + + + + + + + + C + + + + + + 2 + + + + cancel() + + + + + 3 + + + withLatestFrom ( + + + + ) + + + ( + + + + + , + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipAsyncSourcesForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipAsyncSourcesForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipAsyncSourcesForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,321 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + B + + + + + D + + + + + + + 1 + + + 2 + + + + + C + + + + + 4 + + + + + 3 + + + + + + E + + + + + + + A1 + + + + + + + + B2 + + + D4 + + + + + C3 + + + + + + + + + + + + + + + + + + + + + zip ( + + + ) + + + + + + + , + + + ( + + + ) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipDelayErrorFixedSources.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipDelayErrorFixedSources.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipDelayErrorFixedSources.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + zipDelayError + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipDelayErrorIterableSources.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipDelayErrorIterableSources.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipDelayErrorIterableSources.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) + + + ] + + + + + + + , + + + ) + + + + + + , + + + ( + + + ) + + + , + + + + + + + zipDelayError ({ + + + + + + + + + + + ,...} + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipDelayErrorVarSourcesWithZipper.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipDelayErrorVarSourcesWithZipper.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipDelayErrorVarSourcesWithZipper.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + zipDelayError ( + + + ) + + + + + + + , + + + ( + + + ) + + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipFixedSourcesForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipFixedSourcesForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipFixedSourcesForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + B + + + + + D + + + + + + + 1 + + + 2 + + + + + C + + + + + 4 + + + + + 3 + + + + + + + E + + + + zip + + + + + + + + 1 + + + A + + + + + + + + 2 + + + B + + + + + + + + 3 + + + C + + + + + + + + 4 + + + D + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipFixedSourcesForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipFixedSourcesForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipFixedSourcesForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + + 1 + + + + zip + + + + + + + + 1 + + + A + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipIterableSourcesForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipIterableSourcesForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipIterableSourcesForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + B + + + + + D + + + + + + + 1 + + + 2 + + + + + C + + + + + 4 + + + + + 3 + + + + + + E + + + + + + + A1 + + + + + + + + B2 + + + D4 + + + + + C3 + + + + + + zip ( + + + ) + + + + + + + , + + + ( + + + ) + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipIterableSourcesForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipIterableSourcesForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipIterableSourcesForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A1 + + + + + + + + + A + + + + + + 1 + + + + + zip ( + + + ) + + + + + + , + + + ( + + + ) + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipTwoSourcesWithZipperForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipTwoSourcesWithZipperForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipTwoSourcesWithZipperForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A1 + + + + + + + + + B2 + + + D4 + + + + + + + + + + + A + + + + + B + + + + + D + + + + + + + 1 + + + 2 + + + + + C + + + + + 4 + + + + + 3 + + + + + C3 + + + + + + E + + + + + + zip ( + + + ) + + + + + + + , + + + ( + + + ) + + + + + + + + + + + + + , + + + , + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipTwoSourcesWithZipperForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipTwoSourcesWithZipperForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipTwoSourcesWithZipperForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A1 + + + + + + + + + A + + + + + + 1 + + + + + zip ( + + + ) + + + + + + + , + + + ( + + + ) + + + + + + + + + + + , + + + , + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipVarSourcesWithZipperForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipVarSourcesWithZipperForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipVarSourcesWithZipperForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + zip ( + + + ... ) + + + + + + , + + + + + + , + + + + + A + + + + + + 1 + + + , + + + + + A1 + + + + + + + + + , + + + ( + + + ) + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWhenForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWhenForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWhenForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + + 1 + + + zipWhen ( + + + + + + ) + + + + + + + + + 1 + + + A + + + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWhenWithZipperForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWhenWithZipperForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWhenWithZipperForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + + 1 + + + zipWhen ( + + + + + + ) + + + + + + + + + + + + , + + + ( + + + ) + + + + + , + + + + A1 + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithIterableForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithIterableForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithIterableForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + zipWithIterable({ + + + + + + A + + + + + B + + + + + + A + + + + + + + + + + + + + + + + + + C + + + + + + 1 + + + A + + + + + + 1 + + + A + + + + + + 2 + + + B + + + + cancel() + + + } ) + + + , + + + + 1 + + + + 2 + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithIterableUsingZipperForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithIterableUsingZipperForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithIterableUsingZipperForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + B + + + + + + A + + + + + + + + + C + + + + cancel() + + + zipWithIterable({ + + + , + + + } , ( + + + + + + , + + + ) + + + + ) + + + + 1 + + + + 2 + + + + + + + + A1 + + + + + + + + B2 + + + A1 + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + zipWith + + + + + A + + + + B + + + + D + + + + + + + 1 + + + 2 + + + + C + + + + + 4 + + + + + 3 + + + + + E + + + + + + + + + + 1 + + + A + + + + + + 2 + + + B + + + + + + 3 + + + C + + + + + + 4 + + + D + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + zipWith + + + + + A + + + + + + 1 + + + + + + 1 + + + A + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherUsingZipperForFlux.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherUsingZipperForFlux.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherUsingZipperForFlux.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A1 + + + + + + + + + B2 + + + D4 + + + + + + + + + + A + + + + B + + + + D + + + + + + + 1 + + + 2 + + + + C + + + + + 4 + + + + + 3 + + + + + C3 + + + + + E + + + + + zipWith ( + + + ) + + + + + + + , + + + ( + + + ) + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherUsingZipperForMono.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherUsingZipperForMono.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/doc-files/marbles/zipWithOtherUsingZipperForMono.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + + + + 1 + + + + + A1 + + + + + zipWith ( + + + ) + + + + + + + , + + + ( + + + ) + + + + Index: 3rdParty_sources/reactor/reactor/core/publisher/package-info.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/publisher/package-info.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/publisher/package-info.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +/** + * Provide for + * {@link reactor.core.publisher.Flux}, {@link reactor.core.publisher.Mono} composition + * API and {@link org.reactivestreams.Processor} implementations + * + *

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 + */ +@NonNullApi +package reactor.core.publisher; + +import reactor.util.annotation.NonNullApi; \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/core/scheduler/BoundedElasticScheduler.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/BoundedElasticScheduler.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/BoundedElasticScheduler.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,822 @@ +/* + * Copyright (c) 2019-2021 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.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.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; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLong; +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; + +/** + * Scheduler that hosts a pool of 0-N single-threaded {@link BoundedScheduledExecutorService} and exposes workers + * backed by these executors, making it suited for moderate amount of blocking work. Note that requests for workers + * will pick an executor in a round-robin fashion, so tasks from a given worker might arbitrarily be impeded by + * long-running tasks of a sibling worker (and tasks are pinned to a given executor, so they won't be stolen + * by an idle executor). + * + * This scheduler is time-capable (can schedule with delay / periodically). + * + * @author Simon Baslé + */ +final class BoundedElasticScheduler implements Scheduler, Scannable { + + static final int DEFAULT_TTL_SECONDS = 60; + + 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; + }; + + 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 ScheduledExecutorService evictor; + static final AtomicReferenceFieldUpdater EVICTOR = + AtomicReferenceFieldUpdater.newUpdater(BoundedElasticScheduler.class, ScheduledExecutorService.class, "evictor"); + + /** + * This constructor lets define millisecond-grained TTLs and a custom {@link Clock}, + * which can be useful for tests. + */ + BoundedElasticScheduler(int maxThreads, int maxTaskQueuedPerThread, + ThreadFactory threadFactory, long ttlMillis, Clock clock) { + if (ttlMillis <= 0) { + throw new IllegalArgumentException("TTL must be strictly positive, was " + ttlMillis + "ms"); + } + if (maxThreads <= 0) { + throw new IllegalArgumentException("maxThreads must be strictly positive, was " + maxThreads); + } + if (maxTaskQueuedPerThread <= 0) { + throw new IllegalArgumentException("maxTaskQueuedPerThread must be strictly positive, was " + maxTaskQueuedPerThread); + } + this.maxThreads = maxThreads; + this.maxTaskQueuedPerThread = maxTaskQueuedPerThread; + this.factory = threadFactory; + this.clock = Objects.requireNonNull(clock, "A Clock must be provided"); + this.ttlMillis = ttlMillis; + + this.boundedServices = SHUTDOWN; //initially disposed, EVICTOR is also null + } + + /** + * 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 + * can contribute to the task queue size. + * + * @param maxThreads the maximum number of backing threads to spawn, must be strictly positive + * @param maxTaskQueuedPerThread the maximum amount of tasks an executor can queue up + * @param factory the {@link ThreadFactory} to name the backing threads + * @param ttlSeconds the time-to-live (TTL) of idle threads, in seconds + */ + BoundedElasticScheduler(int maxThreads, int maxTaskQueuedPerThread, ThreadFactory factory, int ttlSeconds) { + this(maxThreads, maxTaskQueuedPerThread, factory, ttlSeconds * 1000L, + Clock.tickSeconds(BoundedServices.ZONE_UTC)); + } + + /** + * Instantiates the default {@link ScheduledExecutorService} for the scheduler + * ({@code Executors.newScheduledThreadPoolExecutor} with core and max pool size of 1). + */ + BoundedScheduledExecutorService createBoundedExecutorService() { + return new BoundedScheduledExecutorService(this.maxTaskQueuedPerThread, this.factory); + } + + @Override + public boolean isDisposed() { + return BOUNDED_SERVICES.get(this) == SHUTDOWN; + } + + @Override + public void start() { + for (;;) { + BoundedServices services = BOUNDED_SERVICES.get(this); + if (services != SHUTDOWN) { + return; + } + 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(); + } + return; + } + } + } + + @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(); + } + services.dispose(); + } + } + + @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); + } + + @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); + } + + @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); + } + + @Override + public String toString() { + StringBuilder ts = new StringBuilder(Schedulers.BOUNDED_ELASTIC) + .append('('); + if (factory instanceof ReactorThreadFactory) { + ts.append('\"').append(((ReactorThreadFactory) factory).get()).append("\","); + } + ts.append("maxThreads=").append(maxThreads) + .append(",maxTaskQueuedPerThread=").append(maxTaskQueuedPerThread == Integer.MAX_VALUE ? "unbounded" : maxTaskQueuedPerThread) + .append(",ttl="); + if (ttlMillis < 1000) { + ts.append(ttlMillis).append("ms)"); + } + else { + ts.append(ttlMillis / 1000).append("s)"); + } + return ts.toString(); + } + + /** + * @return a best effort total count of the spinned up executors + */ + int estimateSize() { + return BOUNDED_SERVICES.get(this).get(); + } + + /** + * @return a best effort total count of the busy executors + */ + int estimateBusy() { + return BOUNDED_SERVICES.get(this).busyQueue.size(); + } + + /** + * @return a best effort total count of the idle executors + */ + int estimateIdle() { + return BOUNDED_SERVICES.get(this).idleQueue.size(); + } + + /** + * Best effort snapshot of the remaining queue capacity for pending tasks across all the backing executors. + * + * @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; + int totalTaskCapacity = maxTaskQueuedPerThread * maxThreads; + for (BoundedState state : busyQueue) { + int stateQueueSize = state.estimateQueueSize(); + if (stateQueueSize >= 0) { + totalTaskCapacity -= stateQueueSize; + } + else { + return -1; + } + } + return totalTaskCapacity; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.BUFFERED) return estimateSize(); + if (key == Attr.CAPACITY) return maxThreads; + if (key == Attr.NAME) return this.toString(); + + return null; + } + + @Override + public Stream inners() { + BoundedServices services = BOUNDED_SERVICES.get(this); + return Stream.concat(services.busyQueue.stream(), services.idleQueue.stream()) + .filter(obj -> obj != null && obj != CREATING); + } + + @Override + public Worker createWorker() { + BoundedState picked = BOUNDED_SERVICES.get(this) + .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 implements Disposable { + + /** + * Constant for this counter of live executors to reflect the whole pool has been + * stopped. + */ + static final int DISPOSED = -1; + + /** + * The {@link ZoneId} used for clocks. Since the {@link Clock} is only used to ensure + * TTL cleanup is executed every N seconds, the zone doesn't really matter, hence UTC. + * @implNote Note that {@link ZoneId#systemDefault()} isn't used since it triggers disk read, + * contrary to {@link ZoneId#of(String)}. + */ + static final ZoneId ZONE_UTC = ZoneId.of("UTC"); + + + final BoundedElasticScheduler parent; + //duplicated Clock field from parent so that SHUTDOWN can be instantiated and partially used + final Clock clock; + final Deque idleQueue; + final PriorityBlockingQueue busyQueue; + + //constructor for SHUTDOWN + private BoundedServices() { + this.parent = null; + this.clock = Clock.fixed(Instant.EPOCH, ZONE_UTC); + this.busyQueue = new PriorityBlockingQueue<>(); + this.idleQueue = new ConcurrentLinkedDeque<>(); + } + + 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<>(); + } + + /** + * Trigger the eviction by computing the oldest acceptable timestamp and letting each {@link BoundedState} + * check (and potentially shutdown) itself. + */ + void eviction() { + final long evictionTimestamp = parent.clock.millis(); + List idleCandidates = new ArrayList<>(idleQueue); + for (BoundedState candidate : idleCandidates) { + if (candidate.tryEvict(evictionTimestamp, parent.ttlMillis)) { + idleQueue.remove(candidate); + decrementAndGet(); + } + } + } + + /** + * 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. + * + * @return the picked {@link BoundedState} + */ + BoundedState pick() { + for (;;) { + int a = get(); + if (a == DISPOSED) { + return CREATING; //synonym for shutdown, since the underlying executor is shut down + } + + if (!idleQueue.isEmpty()) { + //try to find an idle resource + BoundedState bs = idleQueue.pollLast(); + if (bs != null && bs.markPicked()) { + busyQueue.add(bs); + return bs; + } + //else optimistically retry (implicit continue here) + } + else if (a < parent.maxThreads) { + //try to build a new resource + if (compareAndSet(a, a + 1)) { + ScheduledExecutorService s = Schedulers.decorateExecutorService(parent, parent.createBoundedExecutorService()); + BoundedState newState = new BoundedState(this, s); + if (newState.markPicked()) { + busyQueue.add(newState); + return newState; + } + } + //else optimistically retry (implicit continue here) + } + else { + //pick the least busy one + BoundedState s = busyQueue.poll(); + 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); + } + } + + @Override + public boolean isDisposed() { + return get() == DISPOSED; + } + + @Override + public void dispose() { + set(DISPOSED); + idleQueue.forEach(BoundedState::shutdown); + busyQueue.forEach(BoundedState::shutdown); + } + } + + /** + * A class that encapsulate state around the {@link BoundedScheduledExecutorService} and + * atomically marking them picked/idle. + */ + static class BoundedState implements Disposable, Scannable { + + /** + * Constant for this counter of backed workers to reflect the given executor has + * been marked for eviction. + */ + static final int EVICTED = -1; + + final BoundedServices parent; + final ScheduledExecutorService executor; + + long idleSinceTimestamp = -1L; + + volatile int markCount; + static final AtomicIntegerFieldUpdater MARK_COUNT = AtomicIntegerFieldUpdater.newUpdater(BoundedState.class, "markCount"); + + BoundedState(BoundedServices parent, ScheduledExecutorService executor) { + this.parent = parent; + this.executor = executor; + } + + /** + * @return the queue size if the executor is a {@link ScheduledThreadPoolExecutor}, -1 otherwise + */ + int estimateQueueSize() { + if (executor instanceof ScheduledThreadPoolExecutor) { + return ((ScheduledThreadPoolExecutor) executor).getQueue().size(); + } + return -1; + } + + /** + * Try to mark this {@link BoundedState} as picked. + * + * @return true if this state could atomically be marked as picked, false if + * eviction started on it in the meantime + */ + boolean markPicked() { + for(;;) { + int i = MARK_COUNT.get(this); + if (i == EVICTED) { + return false; //being evicted + } + if (MARK_COUNT.compareAndSet(this, i, i + 1)) { + return true; + } + } + } + + /** + * Check if this {@link BoundedState} should be evicted by comparing its idleSince + * timestamp to the evictionTimestamp and comparing the difference with the + * given ttlMillis. When eligible for eviction, the executor is shut down and the + * method returns true (to remove the state from the array). + * + * @param evictionTimestamp the timestamp at which the eviction process is running + * @param ttlMillis the maximum idle duration + * @return true if this {@link BoundedState} has shut down itself as part of eviction, false otherwise + */ + boolean tryEvict(long evictionTimestamp, long ttlMillis) { + long idleSince = this.idleSinceTimestamp; + if (idleSince < 0) return false; + long elapsed = evictionTimestamp - idleSince; + if (elapsed >= ttlMillis) { + if (MARK_COUNT.compareAndSet(this, 0, EVICTED)) { + executor.shutdownNow(); + return true; + } + } + return false; + } + + /** + * Release the {@link BoundedState}, ie atomically decrease the counter of times it has been picked + * and mark as idle if that counter reaches 0. + * 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 #dispose() + */ + void release() { + int picked = MARK_COUNT.decrementAndGet(this); + if (picked < 0) { + //picked == -1 means being evicted, do nothing in that case + return; + } + + if (picked == 0) { + //we released enough that this BoundedState is now idle + this.idleSinceTimestamp = parent.clock.millis(); + parent.setIdle(this); + } + else { + //still picked by at least one worker, defensively ensure timestamp is not set + this.idleSinceTimestamp = -1L; + } + } + + /** + * Forcibly shut down the executor and mark that {@link BoundedState} as evicted. + * + * @see #release() + * @see #dispose() + */ + void shutdown() { + this.idleSinceTimestamp = -1L; + MARK_COUNT.set(this, EVICTED); + this.executor.shutdownNow(); + } + + /** + * An alias for {@link #release()}. + */ + @Override + public void dispose() { + this.release(); + } + + /** + * Is this {@link BoundedState} still in use by workers. + * + * @return true if in use, false if it has been disposed enough times + */ + @Override + public boolean isDisposed() { + return MARK_COUNT.get(this) <= 0; + } + + @Override + public Object scanUnsafe(Attr key) { + return Schedulers.scanExecutor(executor, key); + } + + @Override + public String toString() { + return "BoundedState@" + System.identityHashCode(this) + "{" + " backing=" +MARK_COUNT.get(this) + ", idleSince=" + idleSinceTimestamp + ", executor=" + executor + '}'; + } + } + + /** + * A {@link ScheduledThreadPoolExecutor} wrapper enforcing a bound on the task + * queue size. Excessive task queue growth yields {@link + * RejectedExecutionException} errors. {@link RejectedExecutionHandler}s are + * not supported since they expect a {@link ThreadPoolExecutor} in their + * arguments. + * + *

Java Standard library unfortunately doesn't provide any {@link + * ScheduledExecutorService} implementations that one can provide a bound on + * the task queue. This shortcoming is prone to hide backpressure problems. See + * the + * relevant concurrency-interest discussion for {@link java.util.concurrent} + * lead Doug Lea's tip for enforcing a bound via {@link + * ScheduledThreadPoolExecutor#getQueue()}. + */ + static final class BoundedScheduledExecutorService extends ScheduledThreadPoolExecutor + implements Scannable { + + final int queueCapacity; + + BoundedScheduledExecutorService(int queueCapacity, ThreadFactory factory) { + super(1, factory); + setMaximumPoolSize(1); + setRemoveOnCancelPolicy(true); + if (queueCapacity < 1) { + throw new IllegalArgumentException( + "was expecting a non-zero positive queue capacity"); + } + this.queueCapacity = queueCapacity; + } + + @Override + public Object scanUnsafe(Attr key) { + if (Attr.TERMINATED == key) return isTerminated(); + if (Attr.BUFFERED == key) return getQueue().size(); + if (Attr.CAPACITY == key) return this.queueCapacity; + return null; + } + + @Override + public String toString() { + int queued = getQueue().size(); + long completed = getCompletedTaskCount(); + String state = getActiveCount() > 0 ? "ACTIVE" : "IDLE"; + if (this.queueCapacity == Integer.MAX_VALUE) { + return "BoundedScheduledExecutorService{" + state + ", queued=" + queued + "/unbounded, completed=" + completed + '}'; + } + return "BoundedScheduledExecutorService{" + state + ", queued=" + queued + "/" + queueCapacity + ", completed=" + completed + '}'; + } + + private void ensureQueueCapacity(int taskCount) { + if (queueCapacity == Integer.MAX_VALUE) { + return; + } + int queueSize = super.getQueue().size(); + if ((queueSize + taskCount) > queueCapacity) { + throw Exceptions.failWithRejected("Task capacity of bounded elastic scheduler reached while scheduling " + taskCount + " tasks (" + (queueSize + taskCount) + "/" + queueCapacity + ")"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized ScheduledFuture schedule( + Runnable command, + long delay, + TimeUnit unit) { + ensureQueueCapacity(1); + return super.schedule(command, delay, unit); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized ScheduledFuture schedule( + Callable callable, + long delay, + TimeUnit unit) { + ensureQueueCapacity(1); + return super.schedule(callable, delay, unit); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized ScheduledFuture scheduleAtFixedRate( + Runnable command, + long initialDelay, + long period, + TimeUnit unit) { + ensureQueueCapacity(1); + return super.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + /** + * {@inheritDoc} + */ + @Override + public ScheduledFuture scheduleWithFixedDelay( + Runnable command, + long initialDelay, + long delay, + TimeUnit unit) { + ensureQueueCapacity(1); + return super.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown() { + super.shutdown(); + } + + /** + * {@inheritDoc} + */ + @Override + public List shutdownNow() { + return super.shutdownNow(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isShutdown() { + return super.isShutdown(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isTerminated() { + return super.isTerminated(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + return super.awaitTermination(timeout, unit); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized Future submit(Callable task) { + ensureQueueCapacity(1); + return super.submit(task); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized Future submit(Runnable task, T result) { + ensureQueueCapacity(1); + return super.submit(task, result); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized Future submit(Runnable task) { + ensureQueueCapacity(1); + return super.submit(task); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized List> invokeAll( + Collection> tasks) + throws InterruptedException { + ensureQueueCapacity(tasks.size()); + return super.invokeAll(tasks); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized List> invokeAll( + Collection> tasks, + long timeout, + TimeUnit unit) + throws InterruptedException { + ensureQueueCapacity(tasks.size()); + return super.invokeAll(tasks, timeout, unit); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized T invokeAny(Collection> tasks) + throws InterruptedException, ExecutionException { + ensureQueueCapacity(tasks.size()); + return super.invokeAny(tasks); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized T invokeAny( + Collection> tasks, + long timeout, + TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + ensureQueueCapacity(tasks.size()); + return super.invokeAny(tasks, timeout, unit); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void execute(Runnable command) { + ensureQueueCapacity(1); + super.submit(command); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/DelegateServiceScheduler.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/DelegateServiceScheduler.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/DelegateServiceScheduler.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2017-2021 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.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Supplier; + +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.util.annotation.NonNull; +import reactor.util.annotation.Nullable; + +/** + * A simple {@link Scheduler} which uses a backing {@link ExecutorService} to schedule + * Runnables for async operators. This scheduler is time-capable (can schedule with a + * delay and/or periodically) if the backing executor is a {@link ScheduledExecutorService}. + * + * @author Stephane Maldini + * @author Simon Baslé + */ +final class DelegateServiceScheduler implements Scheduler, Scannable { + + final String executorName; + final ScheduledExecutorService original; + + @Nullable + volatile ScheduledExecutorService executor; + static final AtomicReferenceFieldUpdater EXECUTOR = + AtomicReferenceFieldUpdater.newUpdater(DelegateServiceScheduler.class, ScheduledExecutorService.class, "executor"); + + 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) { + throw new IllegalStateException("executor is null after implicit start()"); + } + } + return e; + } + + @Override + public Worker createWorker() { + return new ExecutorServiceWorker(getOrCreate()); + } + + @Override + public Disposable schedule(Runnable task) { + return Schedulers.directSchedule(getOrCreate(), task, null, 0L, TimeUnit.MILLISECONDS); + } + + @Override + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + return Schedulers.directSchedule(getOrCreate(), task, null, delay, unit); + } + + @Override + public Disposable schedulePeriodically(Runnable task, + long initialDelay, + long period, + TimeUnit unit) { + return Schedulers.directSchedulePeriodically(getOrCreate(), + task, + initialDelay, + period, + unit); + } + + @Override + public void start() { + EXECUTOR.compareAndSet(this, null, Schedulers.decorateExecutorService(this, original)); + } + + @Override + public boolean isDisposed() { + ScheduledExecutorService e = executor; + return e != null && e.isShutdown(); + } + + @Override + public void dispose() { + ScheduledExecutorService e = executor; + if (e != null) { + e.shutdownNow(); + } + } + + @SuppressWarnings("unchecked") + static ScheduledExecutorService convert(ExecutorService executor) { + if (executor instanceof ScheduledExecutorService) { + return (ScheduledExecutorService) executor; + } + return new UnsupportedScheduledExecutorService(executor); + } + + @Override + public Object scanUnsafe(Attr key) { + 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); + } + return null; + } + + @Override + public String toString() { + return Schedulers.FROM_EXECUTOR_SERVICE + '(' + executorName + ')'; + } + + static final class UnsupportedScheduledExecutorService + implements ScheduledExecutorService, Supplier { + + final ExecutorService exec; + + UnsupportedScheduledExecutorService(ExecutorService exec) { + this.exec = exec; + } + + @Override + public ExecutorService get() { + return exec; + } + + @Override + public void shutdown() { + exec.shutdown(); + } + + @NonNull + @Override + public List shutdownNow() { + return exec.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return exec.isShutdown(); + } + + @Override + public boolean isTerminated() { + return exec.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, @NonNull TimeUnit unit) + throws InterruptedException { + return exec.awaitTermination(timeout, unit); + } + + @NonNull + @Override + public Future submit(@NonNull Callable task) { + return exec.submit(task); + } + + @NonNull + @Override + public Future submit(@NonNull Runnable task, T result) { + return exec.submit(task, result); + } + + @NonNull + @Override + public Future submit(@NonNull Runnable task) { + return exec.submit(task); + } + + @NonNull + @Override + public List> invokeAll(@NonNull Collection> tasks) + throws InterruptedException { + return exec.invokeAll(tasks); + } + + @NonNull + @Override + public List> invokeAll(@NonNull Collection> tasks, + long timeout, + @NonNull TimeUnit unit) throws InterruptedException { + return exec.invokeAll(tasks, timeout, unit); + } + + @NonNull + @Override + public T invokeAny(@NonNull Collection> tasks) + throws InterruptedException, ExecutionException { + return exec.invokeAny(tasks); + } + + @Override + public T invokeAny(@NonNull Collection> tasks, + long timeout, + @NonNull TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return exec.invokeAny(tasks, timeout, unit); + } + + @Override + public void execute(@NonNull Runnable command) { + exec.execute(command); + } + + @NonNull + @Override + public ScheduledFuture schedule(@NonNull Runnable command, + long delay, + @NonNull TimeUnit unit) { + throw Exceptions.failWithRejectedNotTimeCapable(); + } + + @NonNull + @Override + public ScheduledFuture schedule(@NonNull Callable callable, + long delay, + @NonNull TimeUnit unit) { + throw Exceptions.failWithRejectedNotTimeCapable(); + } + + @NonNull + @Override + public ScheduledFuture scheduleAtFixedRate(@NonNull Runnable command, + long initialDelay, + long period, + @NonNull TimeUnit unit) { + throw Exceptions.failWithRejectedNotTimeCapable(); + } + + @NonNull + @Override + public ScheduledFuture scheduleWithFixedDelay(@NonNull Runnable command, + long initialDelay, + long delay, + @NonNull TimeUnit unit) { + throw Exceptions.failWithRejectedNotTimeCapable(); + } + + @Override + public String toString() { + return exec.toString(); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/DelegatingScheduledExecutorService.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/DelegatingScheduledExecutorService.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/DelegatingScheduledExecutorService.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2020-2021 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.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +class DelegatingScheduledExecutorService implements ScheduledExecutorService { + + final ScheduledExecutorService scheduledExecutorService; + + DelegatingScheduledExecutorService(ScheduledExecutorService service) { + scheduledExecutorService = service; + } + + @Override + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return scheduledExecutorService.schedule(command, delay, unit); + } + + @Override + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + return scheduledExecutorService.schedule(callable, delay, unit); + } + + @Override + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + return scheduledExecutorService.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + @Override + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + return scheduledExecutorService.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + + @Override + public void shutdown() { + scheduledExecutorService.shutdown(); + } + + @Override + public List shutdownNow() { + return scheduledExecutorService.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return scheduledExecutorService.isShutdown(); + } + + @Override + public boolean isTerminated() { + return scheduledExecutorService.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return scheduledExecutorService.awaitTermination(timeout, unit); + } + + @Override + public Future submit(Callable task) { + return scheduledExecutorService.submit(task); + } + + @Override + public Future submit(Runnable task, T result) { + return scheduledExecutorService.submit(task, result); + } + + @Override + public Future submit(Runnable task) { + return scheduledExecutorService.submit(task); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + return scheduledExecutorService.invokeAll(tasks); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return scheduledExecutorService.invokeAll(tasks, timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return scheduledExecutorService.invokeAny(tasks); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return scheduledExecutorService.invokeAny(tasks, timeout, unit); + } + + @Override + public void execute(Runnable command) { + scheduledExecutorService.execute(command); + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/ElasticScheduler.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/ElasticScheduler.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ElasticScheduler.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2016-2021 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.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; + +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Dynamically creates ScheduledExecutorService-based Workers and caches the thread pools, reusing + * them once the Workers have been shut down. This scheduler is time-capable (can schedule + * with delay / periodically). + *

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

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

+ * This scheduler is not restartable (may be later). + * + * @author Stephane Maldini + * @author Simon Baslé + */ +// To be removed in 3.5 +final class ElasticScheduler implements Scheduler, Scannable { + + static final AtomicLong COUNTER = new AtomicLong(); + + static final ThreadFactory EVICTOR_FACTORY = r -> { + Thread t = new Thread(r, "elastic-evictor-" + COUNTER.incrementAndGet()); + t.setDaemon(true); + return t; + }; + + static final CachedService SHUTDOWN = new CachedService(null); + + static final int DEFAULT_TTL_SECONDS = 60; + + final ThreadFactory factory; + + final int ttlSeconds; + + + final Deque cache; + + final Queue all; + + ScheduledExecutorService evictor; + + + volatile boolean shutdown; + + ElasticScheduler(ThreadFactory factory, int ttlSeconds) { + if (ttlSeconds < 0) { + throw new IllegalArgumentException("ttlSeconds must be positive, was: " + ttlSeconds); + } + this.ttlSeconds = ttlSeconds; + this.factory = factory; + this.cache = new ConcurrentLinkedDeque<>(); + this.all = new ConcurrentLinkedQueue<>(); + //evictor is now started in `start()`. make it look like it is constructed shutdown + this.shutdown = true; + } + + /** + * Instantiates the default {@link ScheduledExecutorService} for the ElasticScheduler + * ({@code Executors.newScheduledThreadPoolExecutor} with core and max pool size of 1). + */ + public ScheduledExecutorService createUndecoratedService() { + ScheduledThreadPoolExecutor poolExecutor = new ScheduledThreadPoolExecutor(1, factory); + poolExecutor.setMaximumPoolSize(1); + poolExecutor.setRemoveOnCancelPolicy(true); + return poolExecutor; + } + + @Override + public void start() { + if (!shutdown) { + return; + } + this.evictor = Executors.newScheduledThreadPool(1, EVICTOR_FACTORY); + this.evictor.scheduleAtFixedRate(this::eviction, + ttlSeconds, + ttlSeconds, + TimeUnit.SECONDS); + this.shutdown = false; + } + + @Override + public boolean isDisposed() { + return shutdown; + } + + @Override + public void dispose() { + if (shutdown) { + return; + } + shutdown = true; + + evictor.shutdownNow(); + + cache.clear(); + + CachedService cached; + + while ((cached = all.poll()) != null) { + cached.exec.shutdownNow(); + } + } + + CachedService pick() { + if (shutdown) { + return SHUTDOWN; + } + CachedService result; + ScheduledExecutorServiceExpiry e = cache.pollLast(); + if (e != null) { + return e.cached; + } + + result = new CachedService(this); + all.offer(result); + if (shutdown) { + all.remove(result); + return SHUTDOWN; + } + return result; + } + + @Override + public Disposable schedule(Runnable task) { + CachedService cached = pick(); + + return Schedulers.directSchedule(cached.exec, + task, + cached, + 0L, + TimeUnit.MILLISECONDS); + } + + @Override + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + CachedService cached = pick(); + + return Schedulers.directSchedule(cached.exec, + task, + cached, + delay, + unit); + } + + @Override + public Disposable schedulePeriodically(Runnable task, long initialDelay, long period, TimeUnit unit) { + CachedService cached = pick(); + + return Disposables.composite(Schedulers.directSchedulePeriodically(cached.exec, + task, + initialDelay, + period, + unit), cached); + } + + @Override + public String toString() { + StringBuilder ts = new StringBuilder(Schedulers.ELASTIC) + .append('('); + if (factory instanceof ReactorThreadFactory) { + ts.append('\"').append(((ReactorThreadFactory) factory).get()).append('\"'); + } + ts.append(')'); + return ts.toString(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.CAPACITY) return Integer.MAX_VALUE; + if (key == Attr.BUFFERED) return cache.size(); //BUFFERED: number of workers alive + if (key == Attr.NAME) return this.toString(); + + return null; + } + + @Override + public Stream inners() { + return cache.stream() + .map(cached -> cached.cached); + } + + @Override + public Worker createWorker() { + return new ElasticWorker(pick()); + } + + void eviction() { + long now = System.currentTimeMillis(); + + List list = new ArrayList<>(cache); + for (ScheduledExecutorServiceExpiry e : list) { + if (e.expireMillis < now) { + if (cache.remove(e)) { + e.cached.exec.shutdownNow(); + all.remove(e.cached); + } + } + } + } + + static final class CachedService implements Disposable, Scannable { + + final ElasticScheduler parent; + final ScheduledExecutorService exec; + + CachedService(@Nullable ElasticScheduler parent) { + this.parent = parent; + if (parent != null) { + this.exec = Schedulers.decorateExecutorService(parent, parent.createUndecoratedService()); + } + else { + this.exec = Executors.newSingleThreadScheduledExecutor(); + this.exec.shutdownNow(); + } + } + + @Override + public void dispose() { + if (exec != null) { + if (this != SHUTDOWN && !parent.shutdown) { + ScheduledExecutorServiceExpiry e = new + ScheduledExecutorServiceExpiry(this, + System.currentTimeMillis() + parent.ttlSeconds * 1000L); + parent.cache.offerLast(e); + if (parent.shutdown) { + if (parent.cache.remove(e)) { + exec.shutdownNow(); + } + } + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.NAME) return parent.scanUnsafe(key); + if (key == Attr.PARENT) return parent; + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.CAPACITY) { + //assume 1 if unknown, otherwise use the one from underlying executor + Integer capacity = (Integer) Schedulers.scanExecutor(exec, key); + if (capacity == null || capacity == -1) return 1; + } + return Schedulers.scanExecutor(exec, key); + } + } + + static final class ScheduledExecutorServiceExpiry { + + final CachedService cached; + final long expireMillis; + + ScheduledExecutorServiceExpiry(CachedService cached, long expireMillis) { + this.cached = cached; + this.expireMillis = expireMillis; + } + } + + static final class ElasticWorker extends AtomicBoolean implements Worker, Scannable { + + final CachedService cached; + + final Disposable.Composite tasks; + + ElasticWorker(CachedService cached) { + this.cached = cached; + this.tasks = Disposables.composite(); + } + + @Override + public Disposable schedule(Runnable task) { + return Schedulers.workerSchedule(cached.exec, + tasks, + task, + 0L, + TimeUnit.MILLISECONDS); + } + + @Override + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + return Schedulers.workerSchedule(cached.exec, tasks, task, delay, unit); + } + + @Override + public Disposable schedulePeriodically(Runnable task, + long initialDelay, + long period, + TimeUnit unit) { + return Schedulers.workerSchedulePeriodically(cached.exec, + tasks, + task, + initialDelay, + period, + unit); + } + + @Override + public void dispose() { + if (compareAndSet(false, true)) { + tasks.dispose(); + cached.dispose(); + } + } + + @Override + public boolean isDisposed() { + return tasks.isDisposed(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.NAME) return cached.scanUnsafe(key) + ".worker"; + if (key == Attr.PARENT) return cached.parent; + + return cached.scanUnsafe(key); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/EmptyCompositeDisposable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/EmptyCompositeDisposable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/EmptyCompositeDisposable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017-2021 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 reactor.core.Disposable; + +import java.util.Collection; + +final class EmptyCompositeDisposable implements Disposable.Composite { + + @Override + public boolean add(Disposable d) { + return false; + } + + @Override + public boolean addAll(Collection ds) { + return false; + } + + @Override + public boolean remove(Disposable d) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/ExecutorScheduler.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/ExecutorScheduler.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ExecutorScheduler.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2016-2021 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.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.util.annotation.Nullable; + +/** + * Wraps a java.util.concurrent.Executor and provides the Scheduler API over it. + *

+ * It supports both non-trampolined worker (for cases where the trampolining happens + * externally) and trampolined worker. This scheduler is NOT time-capable (can't schedule + * with delay / periodically). + * + * @author Stephane Maldini + */ +final class ExecutorScheduler implements Scheduler, Scannable { + + final Executor executor; + final boolean trampoline; + + volatile boolean terminated; + + ExecutorScheduler(Executor executor, boolean trampoline) { + this.executor = executor; + this.trampoline = trampoline; + } + + @Override + public Disposable schedule(Runnable task) { + if(terminated){ + throw Exceptions.failWithRejected(); + } + Objects.requireNonNull(task, "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: + try { + executor.execute(r); + } + catch (Throwable ex) { + if (executor instanceof ExecutorService && ((ExecutorService) executor).isShutdown()) { + terminated = true; + } + Schedulers.handleError(ex); + throw Exceptions.failWithRejected(ex); + } + return r; + } + + @Override + public void dispose() { + terminated = true; + } + + @Override + public boolean isDisposed() { + return terminated; + } + + @Override + public Worker createWorker() { + return trampoline ? new ExecutorSchedulerTrampolineWorker(executor) : + new ExecutorSchedulerWorker(executor); + } + + @Override + public String toString() { + StringBuilder ts = new StringBuilder(Schedulers.FROM_EXECUTOR) + .append('(').append(executor); + if (trampoline) ts.append(",trampolining"); + ts.append(')'); + + return ts.toString(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.NAME) return toString(); + + return null; + } + + /** + * A non-tracked runnable that wraps a task and offers cancel support in the form + * of not executing the task. + *

Since Executor doesn't have cancellation support of its own, the + * ExecutorRunnable will stay in the Executor's queue and be always executed. + */ + static final class ExecutorPlainRunnable extends AtomicBoolean + implements Runnable, Disposable { + + /** */ + private static final long serialVersionUID = 5116223460201378097L; + + final Runnable task; + + ExecutorPlainRunnable(Runnable task) { + this.task = task; + } + + @Override + public void run() { + if (!get()) { + try { + task.run(); + } + catch (Throwable ex) { + Schedulers.handleError(ex); + } + finally { + lazySet(true); + } + } + } + + @Override + public boolean isDisposed() { + return get(); + } + + @Override + public void dispose() { + set(true); + } + } + + /** + * Common interface between the tracking workers to signal the need for removal. + */ + interface WorkerDelete { + + void delete(ExecutorTrackedRunnable r); + } + + /** + * A Runnable that wraps a task and has reference back to its parent worker to + * remove itself once completed or cancelled + */ + static final class ExecutorTrackedRunnable extends AtomicBoolean + implements Runnable, Disposable { + + /** */ + private static final long serialVersionUID = 3503344795919906192L; + + final Runnable task; + final WorkerDelete parent; + + final boolean callRemoveOnFinish; + + ExecutorTrackedRunnable(Runnable task, + WorkerDelete parent, + boolean callRemoveOnFinish) { + this.task = task; + this.parent = parent; + this.callRemoveOnFinish = callRemoveOnFinish; + } + + @Override + public void run() { + if (!get()) { + try { + task.run(); + } + catch (Throwable ex) { + Schedulers.handleError(ex); + } + finally { + if (callRemoveOnFinish) { + dispose(); + } + else { + lazySet(true); + } + } + } + } + + @Override + public void dispose() { + if (compareAndSet(false, true)) { + parent.delete(this); + } + } + + @Override + public boolean isDisposed() { + return get(); + } + } + + /** + * A non-trampolining worker that tracks tasks. + */ + static final class ExecutorSchedulerWorker implements Scheduler.Worker, WorkerDelete, Scannable { + + final Executor executor; + + final Disposable.Composite tasks; + + ExecutorSchedulerWorker(Executor executor) { + this.executor = executor; + this.tasks = Disposables.composite(); + } + + @Override + public Disposable schedule(Runnable task) { + Objects.requireNonNull(task, "task"); + + ExecutorTrackedRunnable r = new ExecutorTrackedRunnable(task, this, true); + if (!tasks.add(r)) { + throw Exceptions.failWithRejected(); + } + + try { + executor.execute(r); + } + catch (Throwable ex) { + tasks.remove(r); + Schedulers.handleError(ex); + throw Exceptions.failWithRejected(ex); + } + + return r; + } + + @Override + public void dispose() { + tasks.dispose(); + } + + @Override + public boolean isDisposed() { + return tasks.isDisposed(); + } + + @Override + public void delete(ExecutorTrackedRunnable r) { + tasks.remove(r); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.BUFFERED) return tasks.size(); + if (key == Attr.PARENT) return (executor instanceof Scannable) ? executor : null; + if (key == Attr.NAME) { + //hack to recognize the SingleWorker + if (executor instanceof SingleWorkerScheduler) return executor + ".worker"; + return Schedulers.FROM_EXECUTOR + "(" + executor + ").worker"; + } + + return Schedulers.scanExecutor(executor, key); + } + } + + /** + * A trampolining worker that tracks tasks. + */ + static final class ExecutorSchedulerTrampolineWorker + implements Scheduler.Worker, WorkerDelete, Runnable, Scannable { + + final Executor executor; + + final Queue queue; + + volatile boolean terminated; + + volatile int wip; + static final AtomicIntegerFieldUpdater WIP = + AtomicIntegerFieldUpdater.newUpdater(ExecutorSchedulerTrampolineWorker.class, + "wip"); + + ExecutorSchedulerTrampolineWorker(Executor executor) { + this.executor = executor; + this.queue = new ConcurrentLinkedQueue<>(); + } + + @Override + public Disposable schedule(Runnable task) { + Objects.requireNonNull(task, "task"); + if (terminated) { + throw Exceptions.failWithRejected(); + } + + ExecutorTrackedRunnable r = new ExecutorTrackedRunnable(task, this, false); + synchronized (this) { + if (terminated) { + throw Exceptions.failWithRejected(); + } + queue.offer(r); + } + + if (WIP.getAndIncrement(this) == 0) { + try { + executor.execute(this); + } + catch (Throwable ex) { + r.dispose(); + Schedulers.handleError(ex); + throw Exceptions.failWithRejected(ex); + } + } + + return r; + } + + @Override + public void dispose() { + if (terminated) { + return; + } + terminated = true; + final Queue q = queue; + + ExecutorTrackedRunnable r; + + while ((r = q.poll()) != null && !q.isEmpty()) { + r.dispose(); + } + } + + @Override + public boolean isDisposed() { + return terminated; + } + + @Override + public void delete(ExecutorTrackedRunnable r) { + synchronized (this) { + if (!terminated) { + queue.remove(r); + } + } + } + + @Override + public void run() { + final Queue q = queue; + + for (; ; ) { + + int e = 0; + int r = wip; + + while (e != r) { + if (terminated) { + return; + } + ExecutorTrackedRunnable task = q.poll(); + + if (task == null) { + break; + } + + task.run(); + + e++; + } + + if (e == r && terminated) { + return; + } + + if (WIP.addAndGet(this, -e) == 0) { + break; + } + } + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.PARENT) return (executor instanceof Scannable) ? executor : null; + if (key == Attr.NAME) return Schedulers.FROM_EXECUTOR + "(" + executor + ",trampolining).worker"; + if (key == Attr.BUFFERED || key == Attr.LARGE_BUFFERED) return queue.size(); + + return Schedulers.scanExecutor(executor, key); + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/ExecutorServiceWorker.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/ExecutorServiceWorker.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ExecutorServiceWorker.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2015-2021 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.TimeUnit; + +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Scannable; + +/** + * @author Stephane Maldini + */ +final class ExecutorServiceWorker implements Scheduler.Worker, Disposable, Scannable { + + /** + * The {@link ScheduledExecutorService} that backs this worker (can be shared) + */ + final ScheduledExecutorService exec; + + /** + * Cleanup tasks to be performed when this worker is {@link Disposable#dispose() disposed}, + * including but not limited to tasks that have been scheduled on the worker. + */ + final Composite disposables; + + + ExecutorServiceWorker(ScheduledExecutorService exec) { + this.exec = exec; + this.disposables = Disposables.composite(); + } + + @Override + public Disposable schedule(Runnable task) { + return Schedulers.workerSchedule(exec, disposables, task, 0L, TimeUnit.MILLISECONDS); + } + + @Override + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + return Schedulers.workerSchedule(exec, disposables, task, delay, unit); + } + + @Override + public Disposable schedulePeriodically(Runnable task, + long initialDelay, + long period, + TimeUnit unit) { + return Schedulers.workerSchedulePeriodically(exec, disposables, + task, + initialDelay, + period, + unit); + } + + @Override + public void dispose() { + disposables.dispose(); + } + + @Override + public boolean isDisposed() { + return disposables.isDisposed(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.BUFFERED) return disposables.size(); + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.NAME) return "ExecutorServiceWorker"; //could be parallel, single or fromExecutorService + + return Schedulers.scanExecutor(exec, key); + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/ImmediateScheduler.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/ImmediateScheduler.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ImmediateScheduler.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016-2021 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 reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.core.Exceptions; +import reactor.core.Scannable; + +/** + * Executes tasks on the caller's thread immediately. + *

+ * Use the ImmediateScheduler.instance() to get a shared, stateless instance of this scheduler. + * This scheduler is NOT time-capable (can't schedule with delay / periodically). + * + * @author Stephane Maldini + */ +final class ImmediateScheduler implements Scheduler, Scannable { + + private static final ImmediateScheduler INSTANCE; + + static { + INSTANCE = new ImmediateScheduler(); + INSTANCE.start(); + } + + public static Scheduler instance() { + return INSTANCE; + } + + private ImmediateScheduler() { + } + + static final Disposable FINISHED = Disposables.disposed(); + + @Override + public Disposable schedule(Runnable task) { + task.run(); + return FINISHED; + } + + @Override + public void dispose() { + //NO-OP + } + + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.NAME) return Schedulers.IMMEDIATE; + + return null; + } + + @Override + public Worker createWorker() { + return new ImmediateSchedulerWorker(); + } + + static final class ImmediateSchedulerWorker implements Scheduler.Worker, Scannable { + + volatile boolean shutdown; + + @Override + public Disposable schedule(Runnable task) { + if (shutdown) { + throw Exceptions.failWithRejected(); + } + task.run(); + return FINISHED; + } + + @Override + public void dispose() { + shutdown = true; + } + + @Override + public boolean isDisposed() { + return shutdown; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return shutdown; + if (key == Attr.NAME) return Schedulers.IMMEDIATE + ".worker"; + + return null; + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/InstantPeriodicWorkerTask.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/InstantPeriodicWorkerTask.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/InstantPeriodicWorkerTask.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2017-2021 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.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import reactor.core.Disposable; +import reactor.util.annotation.Nullable; + +/** + * A runnable task for {@link Scheduler} Workers that can run periodically + **/ +final class InstantPeriodicWorkerTask implements Disposable, Callable { + + final Runnable task; + + final ExecutorService executor; + + static final Composite DISPOSED = new EmptyCompositeDisposable(); + + static final Future CANCELLED = new FutureTask<>(() -> null); + + volatile Future rest; + static final AtomicReferenceFieldUpdater REST = + AtomicReferenceFieldUpdater.newUpdater(InstantPeriodicWorkerTask.class, Future.class, "rest"); + + volatile Future first; + static final AtomicReferenceFieldUpdater FIRST = + AtomicReferenceFieldUpdater.newUpdater(InstantPeriodicWorkerTask.class, Future.class, "first"); + + volatile Composite parent; + static final AtomicReferenceFieldUpdater PARENT = + AtomicReferenceFieldUpdater.newUpdater(InstantPeriodicWorkerTask.class, Composite.class, "parent"); + + Thread thread; + + InstantPeriodicWorkerTask(Runnable task, ExecutorService executor) { + this.task = task; + this.executor = executor; + } + + InstantPeriodicWorkerTask(Runnable task, ExecutorService executor, Composite parent) { + this.task = task; + this.executor = executor; + PARENT.lazySet(this, parent); + } + + @Override + @Nullable + public Void call() { + thread = Thread.currentThread(); + try { + try { + task.run(); + setRest(executor.submit(this)); + } + catch (Throwable ex) { + Schedulers.handleError(ex); + } + } + finally { + thread = null; + } + return null; + } + + void setRest(Future f) { + for (;;) { + Future o = rest; + if (o == CANCELLED) { + f.cancel(thread != Thread.currentThread()); + return; + } + if (REST.compareAndSet(this, o, f)) { + return; + } + } + } + + void setFirst(Future f) { + for (;;) { + Future o = first; + if (o == CANCELLED) { + f.cancel(thread != Thread.currentThread()); + return; + } + if (FIRST.compareAndSet(this, o, f)) { + return; + } + } + } + + @Override + public boolean isDisposed() { + return rest == CANCELLED; + } + + @Override + public void dispose() { + for (;;) { + Future f = first; + if (f == CANCELLED) { + break; + } + if (FIRST.compareAndSet(this, f, CANCELLED)) { + if (f != null) { + f.cancel(thread != Thread.currentThread()); + } + break; + } + } + + for (;;) { + Future f = rest; + if (f == CANCELLED) { + break; + } + if (REST.compareAndSet(this, f, CANCELLED)) { + if (f != null) { + f.cancel(thread != Thread.currentThread()); + } + break; + } + } + + for (;;) { + Composite o = parent; + if (o == DISPOSED || o == null) { + return; + } + if (PARENT.compareAndSet(this, o, DISPOSED)) { + o.remove(this); + return; + } + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/NonBlocking.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/NonBlocking.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/NonBlocking.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016-2021 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; + +/** + * A marker interface that is detected on {@link Thread Threads} while executing Reactor + * blocking APIs, resulting in these calls throwing an exception. + *

+ * See {@link Schedulers#isInNonBlockingThread()}} and + * {@link Schedulers#isNonBlockingThread(Thread)} + * + * @author Simon Baslé + */ +public interface NonBlocking { } Index: 3rdParty_sources/reactor/reactor/core/scheduler/ParallelScheduler.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/ParallelScheduler.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ParallelScheduler.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2016-2021 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.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import reactor.core.Disposable; +import reactor.core.Scannable; + +/** + * Scheduler that hosts a fixed pool of single-threaded ScheduledExecutorService-based workers + * and is suited for parallel work. This scheduler is time-capable (can schedule with + * delay / periodically). + * + * @author Stephane Maldini + * @author Simon Baslé + */ +final class ParallelScheduler implements Scheduler, Supplier, + Scannable { + + 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(); + } + + int roundRobin; + + ParallelScheduler(int n, ThreadFactory factory) { + if (n <= 0) { + throw new IllegalArgumentException("n > 0 required but it was " + n); + } + this.n = n; + this.factory = factory; + } + + /** + * Instantiates the default {@link ScheduledExecutorService} for the ParallelScheduler + * ({@code Executors.newScheduledThreadPoolExecutor} with core and max pool size of 1). + */ + @Override + public ScheduledExecutorService get() { + ScheduledThreadPoolExecutor poolExecutor = new ScheduledThreadPoolExecutor(1, factory); + poolExecutor.setMaximumPoolSize(1); + poolExecutor.setRemoveOnCancelPolicy(true); + return poolExecutor; + } + + @Override + public boolean isDisposed() { + return executors == 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; + } + + 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; + } + } + } + + @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(); + } + } + } + } + + ScheduledExecutorService pick() { + ScheduledExecutorService[] a = executors; + if (a == null) { + start(); + a = executors; + if (a == null) { + throw new IllegalStateException("executors uninitialized after implicit start()"); + } + } + 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; + } + return a[idx]; + } + return TERMINATED; + } + + @Override + public Disposable schedule(Runnable task) { + return Schedulers.directSchedule(pick(), task, null, 0L, TimeUnit.MILLISECONDS); + } + + @Override + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + return Schedulers.directSchedule(pick(), task, null, delay, unit); + } + + @Override + public Disposable schedulePeriodically(Runnable task, + long initialDelay, + long period, + TimeUnit unit) { + return Schedulers.directSchedulePeriodically(pick(), + task, + initialDelay, + period, + unit); + } + + @Override + public String toString() { + StringBuilder ts = new StringBuilder(Schedulers.PARALLEL) + .append('(').append(n); + if (factory instanceof ReactorThreadFactory) { + ts.append(",\"").append(((ReactorThreadFactory) factory).get()).append('\"'); + } + ts.append(')'); + return ts.toString(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.CAPACITY || key == Attr.BUFFERED) return n; //BUFFERED: number of workers doesn't vary + if (key == Attr.NAME) return this.toString(); + + return null; + } + + @Override + public Stream inners() { + return Stream.of(executors) + .map(exec -> key -> Schedulers.scanExecutor(exec, key)); + } + + @Override + public Worker createWorker() { + return new ExecutorServiceWorker(pick()); + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/PeriodicSchedulerTask.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/PeriodicSchedulerTask.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/PeriodicSchedulerTask.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2017-2021 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 reactor.core.Disposable; +import reactor.util.annotation.Nullable; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +final class PeriodicSchedulerTask implements Runnable, Disposable, Callable { + + final Runnable task; + + static final Future CANCELLED = new FutureTask<>(() -> null); + + volatile Future future; + static final AtomicReferenceFieldUpdater FUTURE = + AtomicReferenceFieldUpdater.newUpdater(PeriodicSchedulerTask.class, Future.class, "future"); + + Thread thread; + + PeriodicSchedulerTask(Runnable task) { + this.task = task; + } + + @Override + @Nullable + public Void call() { + thread = Thread.currentThread(); + try { + try { + task.run(); + } + catch (Throwable ex) { + Schedulers.handleError(ex); + } + } + finally { + thread = null; + } + return null; + } + + @Override + public void run() { + call(); + } + + void setFuture(Future f) { + for (;;) { + Future o = future; + if (o == CANCELLED) { + f.cancel(thread != Thread.currentThread()); + return; + } + if (FUTURE.compareAndSet(this, o, f)) { + return; + } + } + } + + @Override + public boolean isDisposed() { + return future == CANCELLED; + } + + @Override + public void dispose() { + for (;;) { + Future f = future; + if (f == CANCELLED) { + break; + } + if (FUTURE.compareAndSet(this, f, CANCELLED)) { + if (f != null) { + f.cancel(thread != Thread.currentThread()); + } + break; + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/PeriodicWorkerTask.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/PeriodicWorkerTask.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/PeriodicWorkerTask.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2017-2021 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 reactor.core.Disposable; +import reactor.util.annotation.Nullable; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +/** + * A runnable task for {@link Scheduler} Workers that can run periodically + **/ +final class PeriodicWorkerTask implements Runnable, Disposable, Callable { + + final Runnable task; + + static final Composite DISPOSED = new EmptyCompositeDisposable(); + + static final Future CANCELLED = new FutureTask<>(() -> null); + + volatile Future future; + static final AtomicReferenceFieldUpdater FUTURE = + AtomicReferenceFieldUpdater.newUpdater(PeriodicWorkerTask.class, Future.class, "future"); + + volatile Composite parent; + static final AtomicReferenceFieldUpdater PARENT = + AtomicReferenceFieldUpdater.newUpdater(PeriodicWorkerTask.class, Composite.class, "parent"); + + Thread thread; + + PeriodicWorkerTask(Runnable task, Composite parent) { + this.task = task; + PARENT.lazySet(this, parent); + } + + @Override + @Nullable + public Void call() { + thread = Thread.currentThread(); + try { + try { + task.run(); + } + catch (Throwable ex) { + Schedulers.handleError(ex); + } + } + finally { + thread = null; + } + return null; + } + + @Override + public void run() { + call(); + } + + void setFuture(Future f) { + for (;;) { + Future o = future; + if (o == CANCELLED) { + f.cancel(thread != Thread.currentThread()); + return; + } + if (FUTURE.compareAndSet(this, o, f)) { + return; + } + } + } + + @Override + public boolean isDisposed() { + return future == CANCELLED; + } + + @Override + public void dispose() { + for (;;) { + Future f = future; + if (f == CANCELLED) { + break; + } + if (FUTURE.compareAndSet(this, f, CANCELLED)) { + if (f != null) { + f.cancel(thread != Thread.currentThread()); + } + break; + } + } + + for (;;) { + Composite o = parent; + if (o == DISPOSED || o == null) { + return; + } + if (PARENT.compareAndSet(this, o, DISPOSED)) { + o.remove(this); + return; + } + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/ReactorBlockHoundIntegration.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/ReactorBlockHoundIntegration.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ReactorBlockHoundIntegration.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2021 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 reactor.blockhound.BlockHound; +import reactor.blockhound.integration.BlockHoundIntegration; + +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * {@link BlockHoundIntegration} with Reactor's scheduling mechanism. + * + * WARNING: this class is not intended to be public, but {@link java.util.ServiceLoader} + * requires it to be so. Public visibility DOES NOT make it part of the public API. + * + * @since 3.3.0 + */ +public final class ReactorBlockHoundIntegration implements BlockHoundIntegration { + + @Override + public void applyTo(BlockHound.Builder builder) { + builder.nonBlockingThreadPredicate(current -> current.or(NonBlocking.class::isInstance)); + + builder.allowBlockingCallsInside(ScheduledThreadPoolExecutor.class.getName() + "$DelayedWorkQueue", "offer"); + builder.allowBlockingCallsInside(ScheduledThreadPoolExecutor.class.getName() + "$DelayedWorkQueue", "take"); + + // Calls ScheduledFutureTask#cancel that may short park in DelayedWorkQueue#remove for getting a lock + builder.allowBlockingCallsInside(SchedulerTask.class.getName(), "dispose"); + + builder.allowBlockingCallsInside(ThreadPoolExecutor.class.getName(), "processWorkerExit"); + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/ReactorThreadFactory.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/ReactorThreadFactory.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/ReactorThreadFactory.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2018-2021 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.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +import reactor.util.annotation.NonNull; +import reactor.util.annotation.Nullable; + +/** + * The standard Reactor {@link ThreadFactory Thread factories} to be used by {@link Scheduler}, + * creating {@link Thread} with a prefix (which can be retrieved with the {@link #get()} method). + * + * @author Simon Baslé + */ +class ReactorThreadFactory implements ThreadFactory, + Supplier, + Thread.UncaughtExceptionHandler { + + final private String name; + final private AtomicLong counterReference; + final private boolean daemon; + final private boolean rejectBlocking; + + @Nullable + final private BiConsumer uncaughtExceptionHandler; + + ReactorThreadFactory(String name, + AtomicLong counterReference, + boolean daemon, + boolean rejectBlocking, + @Nullable BiConsumer uncaughtExceptionHandler) { + this.name = name; + this.counterReference = counterReference; + this.daemon = daemon; + this.rejectBlocking = rejectBlocking; + this.uncaughtExceptionHandler = uncaughtExceptionHandler; + } + + @Override + public final Thread newThread(@NonNull Runnable runnable) { + String newThreadName = name + "-" + counterReference.incrementAndGet(); + Thread t = rejectBlocking + ? new NonBlockingThread(runnable, newThreadName) + : new Thread(runnable, newThreadName); + if (daemon) { + t.setDaemon(true); + } + if (uncaughtExceptionHandler != null) { + t.setUncaughtExceptionHandler(this); + } + return t; + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + if (uncaughtExceptionHandler == null) { + return; + } + + uncaughtExceptionHandler.accept(t,e); + } + + /** + * Get the prefix used for new {@link Thread Threads} created by this {@link ThreadFactory}. + * The factory can also be seen as a {@link Supplier Supplier<String>}. + * + * @return the thread name prefix + */ + @Override + public final String get() { + return name; + } + + static final class NonBlockingThread extends Thread implements NonBlocking { + + public NonBlockingThread(Runnable target, String name) { + super(target, name); + } + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/Scheduler.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/Scheduler.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/Scheduler.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2016-2021 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.Executor; +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. + *

+ * Implementations that use an underlying {@link ExecutorService} or + * {@link ScheduledExecutorService} should decorate it with the relevant {@link Schedulers} hook + * ({@link Schedulers#decorateExecutorService(Scheduler, ScheduledExecutorService)}. + * + * @author Stephane Maldini + * @author Simon Baslé + */ +public interface Scheduler extends Disposable { + + /** + * Schedules the non-delayed execution of the given task on this scheduler. + * + *

+ * This method is safe to be called from multiple threads but there are no + * ordering guarantees between tasks. + * + * @param task the task to execute + * + * @return the {@link Disposable} instance that let's one cancel this particular task. + * If the {@link Scheduler} has been shut down, throw a {@link RejectedExecutionException}. + */ + Disposable schedule(Runnable task); + + /** + * Schedules the execution of the given task with the given delay amount. + * + *

+ * This method is safe to be called from multiple threads but there are no + * ordering guarantees between tasks. + * + * @param task the task to schedule + * @param delay the delay amount, non-positive values indicate non-delayed scheduling + * @param unit the unit of measure of the delay amount + * @return the {@link Disposable} that let's one cancel this particular delayed task, + * or throw a {@link RejectedExecutionException} if the Scheduler is not capable of scheduling with delay. + */ + default Disposable schedule(Runnable task, long delay, TimeUnit unit) { + throw Exceptions.failWithRejectedNotTimeCapable(); + } + + /** + * Schedules a periodic execution of the given task with the given initial delay and period. + * + *

+ * This method is safe to be called from multiple threads but there are no + * ordering guarantees between tasks. + * + *

+ * The periodic execution is at a fixed rate, that is, the first execution will be after the initial + * delay, the second after initialDelay + period, the third after initialDelay + 2 * period, and so on. + * + * @param task the task to schedule + * @param initialDelay the initial delay amount, non-positive values indicate non-delayed scheduling + * @param period the period at which the task should be re-executed + * @param unit the unit of measure of the delay amount + * @return the {@link Disposable} that let's one cancel this particular delayed task, + * or throw a {@link RejectedExecutionException} if the Scheduler is not capable of scheduling periodically. + */ + default Disposable schedulePeriodically(Runnable task, long initialDelay, long period, TimeUnit unit) { + throw Exceptions.failWithRejectedNotTimeCapable(); + } + + /** + * Returns the "current time" notion of this scheduler. + * + *

+ * Implementation Note: The default implementation uses {@link System#currentTimeMillis()} + * when requested with a {@code TimeUnit} of {@link TimeUnit#MILLISECONDS milliseconds} or coarser, and + * {@link System#nanoTime()} otherwise. As a consequence, results should not be interpreted as absolute timestamps + * in the latter case, only monotonicity inside the current JVM can be expected. + *

+ * @param unit the target unit of the current time + * @return the current time value in the target unit of measure + */ + default long now(TimeUnit unit) { + if (unit.compareTo(TimeUnit.MILLISECONDS) >= 0) { + return unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } else { + return unit.convert(System.nanoTime(), TimeUnit.NANOSECONDS); + } + } + + /** + * Creates a worker of this Scheduler. + *

+ * Once the Worker is no longer in use, one should call dispose() on it to + * release any resources the particular Scheduler may have used. + * + * It depends on the implementation, but Scheduler Workers should usually run tasks in + * FIFO order. Some implementations may entirely delegate the scheduling to an + * underlying structure (like an {@link ExecutorService}). + * + * @return the Worker instance. + */ + Worker createWorker(); + + /** + * 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 Scheduler may choose to ignore this instruction. + * + */ + default void 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. + */ + default void start() { + } + + /** + * A worker representing an asynchronous boundary that executes tasks. + * + * @author Stephane Maldini + * @author Simon Baslé + */ + interface Worker extends Disposable { + + /** + * Schedules the task for immediate execution on this worker. + * @param task the task to schedule + * @return the {@link Disposable} instance that let's one cancel this particular task. + * If the Scheduler has been shut down, a {@link RejectedExecutionException} is thrown. + */ + Disposable schedule(Runnable task); + + /** + * Schedules the execution of the given task with the given delay amount. + * + *

+ * This method is safe to be called from multiple threads and tasks are executed in + * some total order. Two tasks scheduled at a same time with the same delay will be + * ordered in FIFO order if the schedule() was called from the same thread or + * in arbitrary order if the schedule() was called from different threads. + * + * @param task the task to schedule + * @param delay the delay amount, non-positive values indicate non-delayed scheduling + * @param unit the unit of measure of the delay amount + * @return the {@link Disposable} that let's one cancel this particular delayed task, + * or throw a {@link RejectedExecutionException} if the Worker is not capable of scheduling with delay. + */ + default Disposable schedule(Runnable task, long delay, TimeUnit unit) { + throw Exceptions.failWithRejectedNotTimeCapable(); + } + + /** + * Schedules a periodic execution of the given task with the given initial delay and period. + * + *

+ * This method is safe to be called from multiple threads. + * + *

+ * The periodic execution is at a fixed rate, that is, the first execution will be after the initial + * delay, the second after initialDelay + period, the third after initialDelay + 2 * period, and so on. + * + * @param task the task to schedule + * @param initialDelay the initial delay amount, non-positive values indicate non-delayed scheduling + * @param period the period at which the task should be re-executed + * @param unit the unit of measure of the delay amount + * @return the {@link Disposable} that let's one cancel this particular delayed task, + * or throw a {@link RejectedExecutionException} if the Worker is not capable of scheduling periodically. + */ + default Disposable schedulePeriodically(Runnable task, long initialDelay, long period, TimeUnit unit) { + throw Exceptions.failWithRejectedNotTimeCapable(); + } + + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerMetricDecorator.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerMetricDecorator.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerMetricDecorator.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018-2021 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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics; +import io.micrometer.core.instrument.search.Search; + +import reactor.core.Disposable; +import reactor.core.Scannable; +import reactor.core.Scannable.Attr; +import reactor.util.Metrics; + +final class SchedulerMetricDecorator + implements BiFunction, + Disposable { + + static final String TAG_SCHEDULER_ID = "reactor.scheduler.id"; + static final String METRICS_DECORATOR_KEY = "reactor.metrics.decorator"; + + final WeakHashMap seenSchedulers = new WeakHashMap<>(); + final Map schedulerDifferentiator = new HashMap<>(); + final WeakHashMap executorDifferentiator = new WeakHashMap<>(); + final MeterRegistry registry; + + SchedulerMetricDecorator() { + registry = Metrics.MicrometerConfiguration.getRegistry(); + } + + @Override + public synchronized ScheduledExecutorService apply(Scheduler scheduler, ScheduledExecutorService service) { + //this is equivalent to `toString`, a detailed name like `parallel("foo", 3)` + String schedulerName = Scannable + .from(scheduler) + .scanOrDefault(Attr.NAME, scheduler.getClass().getName()); + + //we hope that each NAME is unique enough, but we'll differentiate by Scheduler + String schedulerId = + seenSchedulers.computeIfAbsent(scheduler, s -> { + int schedulerDifferentiator = this.schedulerDifferentiator + .computeIfAbsent(schedulerName, k -> new AtomicInteger(0)) + .getAndIncrement(); + + return (schedulerDifferentiator == 0) ? schedulerName + : schedulerName + "#" + schedulerDifferentiator; + }); + + //we now want an executorId unique to a given scheduler + String executorId = schedulerId + "-" + + executorDifferentiator.computeIfAbsent(scheduler, key -> new AtomicInteger(0)) + .getAndIncrement(); + + Tags tags = Tags.of(TAG_SCHEDULER_ID, schedulerId); + + /* + Design note: we assume that a given Scheduler won't apply the decorator twice to the + same ExecutorService. Even though, it would simply create an extraneous meter for + that ExecutorService, which we think is not that bad (compared to paying the price + upfront of also tracking executors instances to deduplicate). The main goal is to + detect Scheduler instances that have already started decorating their executors, + in order to avoid consider two calls in a row as duplicates (yet still being able + to distinguish between two instances with the same name and configuration). + */ + + class MetricsRemovingScheduledExecutorService extends DelegatingScheduledExecutorService { + + MetricsRemovingScheduledExecutorService() { + super(ExecutorServiceMetrics.monitor(registry, service, executorId, tags)); + } + + @Override + public List shutdownNow() { + removeMetrics(); + return super.shutdownNow(); + } + + @Override + public void shutdown() { + removeMetrics(); + super.shutdown(); + } + + void removeMetrics() { + Search.in(registry) + .tag("name", executorId) + .meters() + .forEach(registry::remove); + } + } + return new MetricsRemovingScheduledExecutorService(); + } + + @Override + public void dispose() { + Search.in(registry) + .tagKeys(TAG_SCHEDULER_ID) + .meters() + .forEach(registry::remove); + + //note default isDisposed (returning false) is good enough, since the cleared + //collections can always be reused even though they probably won't + this.seenSchedulers.clear(); + this.schedulerDifferentiator.clear(); + this.executorDifferentiator.clear(); + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerTask.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerTask.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/SchedulerTask.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2017-2021 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.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import reactor.core.Disposable; +import reactor.core.Disposables; +import reactor.util.annotation.Nullable; + +final class SchedulerTask implements Runnable, Disposable, Callable { + + final Runnable task; + + static final Future FINISHED = new FutureTask<>(() -> null); + static final Future CANCELLED = new FutureTask<>(() -> null); + + static final Disposable TAKEN = Disposables.disposed(); + + volatile Future future; + static final AtomicReferenceFieldUpdater FUTURE = + AtomicReferenceFieldUpdater.newUpdater(SchedulerTask.class, Future.class, "future"); + + volatile Disposable parent; + static final AtomicReferenceFieldUpdater PARENT = + AtomicReferenceFieldUpdater.newUpdater(SchedulerTask.class, Disposable.class, "parent"); + + Thread thread; + + SchedulerTask(Runnable task, @Nullable Disposable parent) { + this.task = task; + PARENT.lazySet(this, parent); + } + + @Override + @Nullable + public Void call() { + thread = Thread.currentThread(); + Disposable d = null; + try { + for (;;) { + d = parent; + if (d == TAKEN || d == null) { + break; + } + if (PARENT.compareAndSet(this, d, TAKEN)) { + break; + } + } + try { + task.run(); + } + catch (Throwable ex) { + Schedulers.handleError(ex); + } + } + finally { + thread = null; + Future f; + for (;;) { + f = future; + if (f == CANCELLED || FUTURE.compareAndSet(this, f, FINISHED)) { + break; + } + } + if (d != null) { + d.dispose(); + } + } + return null; + } + + @Override + public void run() { + call(); + } + + void setFuture(Future f) { + for (;;) { + Future o = future; + if (o == FINISHED) { + return; + } + if (o == CANCELLED) { + f.cancel(thread != Thread.currentThread()); + return; + } + if (FUTURE.compareAndSet(this, o, f)) { + return; + } + } + } + + @Override + public boolean isDisposed() { + Future a = future; + return FINISHED == a || CANCELLED == a; + } + + @Override + public void dispose() { + for (;;) { + Future f = future; + if (f == FINISHED || f == CANCELLED) { + break; + } + if (FUTURE.compareAndSet(this, f, CANCELLED)) { + if (f != null) { + f.cancel(thread != Thread.currentThread()); + } + break; + } + } + + Disposable d; + for (;;) { + d = parent; + if (d == TAKEN || d == null) { + break; + } + if (PARENT.compareAndSet(this, d, TAKEN)) { + d.dispose(); + break; + } + } + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/Schedulers.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/Schedulers.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/Schedulers.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,1460 @@ +/* + * Copyright (c) 2016-2021 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.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.micrometer.core.instrument.MeterRegistry; +import reactor.core.Disposable; +import reactor.core.Exceptions; +import reactor.core.Scannable; +import reactor.util.Logger; +import reactor.util.Loggers; +import reactor.util.Metrics; +import reactor.util.annotation.Nullable; + +import static reactor.core.Exceptions.unwrap; + +/** + * {@link Schedulers} provides various {@link Scheduler} flavors usable by {@link + * reactor.core.publisher.Flux#publishOn(Scheduler) publishOn} or {@link reactor.core.publisher.Mono#subscribeOn + * subscribeOn} : + *

+ *

    + *
  • {@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. + * + * @author Stephane Maldini + */ +public abstract class Schedulers { + + /** + * Default pool size, initialized by system property {@code reactor.schedulers.defaultPoolSize} + * and falls back to the number of processors available to the runtime on init. + * + * @see Runtime#availableProcessors() + */ + public static final int DEFAULT_POOL_SIZE = + Optional.ofNullable(System.getProperty("reactor.schedulers.defaultPoolSize")) + .map(Integer::parseInt) + .orElseGet(() -> Runtime.getRuntime().availableProcessors()); + + /** + * Default maximum size for the global {@link #boundedElastic()} {@link Scheduler}, initialized + * by system property {@code reactor.schedulers.defaultBoundedElasticSize} and falls back to 10 x number + * of processors available to the runtime on init. + * + * @see Runtime#availableProcessors() + * @see #boundedElastic() + */ + public static final int DEFAULT_BOUNDED_ELASTIC_SIZE = + Optional.ofNullable(System.getProperty("reactor.schedulers.defaultBoundedElasticSize")) + .map(Integer::parseInt) + .orElseGet(() -> 10 * Runtime.getRuntime().availableProcessors()); + + /** + * Default maximum number of enqueued tasks PER THREAD for the global {@link #boundedElastic()} {@link Scheduler}, + * initialized by system property {@code reactor.schedulers.defaultBoundedElasticQueueSize} and falls back to + * a bound of 100 000 tasks per backing thread. + * + * @see #boundedElastic() + */ + public static final int DEFAULT_BOUNDED_ELASTIC_QUEUESIZE = + Optional.ofNullable(System.getProperty("reactor.schedulers.defaultBoundedElasticQueueSize")) + .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. + * + *

Tasks scheduled with workers of this Scheduler are not guaranteed to run in FIFO + * order and strictly non-concurrently. + * If FIFO order is desired, use trampoline parameter of {@link Schedulers#fromExecutor(Executor, boolean)} + * + * @param executor an {@link Executor} + * + * @return a new {@link Scheduler} + */ + public static Scheduler fromExecutor(Executor executor) { + return fromExecutor(executor, false); + } + + /** + * Create a {@link Scheduler} which uses a backing {@link Executor} to schedule + * Runnables for async operators. + * + * Trampolining here means tasks submitted in a burst are queued by the Worker itself, + * which acts as a sole task from the perspective of the {@link ExecutorService}, + * so no reordering (but also no threading). + * + * @param executor an {@link Executor} + * @param trampoline set to false if this {@link Scheduler} is used by "operators" + * that already conflate {@link Runnable} executions (publishOn, subscribeOn...) + * + * @return a new {@link Scheduler} + */ + public static Scheduler fromExecutor(Executor executor, boolean trampoline) { + if(!trampoline && executor instanceof ExecutorService){ + return fromExecutorService((ExecutorService) executor); + } + final ExecutorScheduler scheduler = new ExecutorScheduler(executor, trampoline); + scheduler.start(); + return scheduler; + } + + /** + * Create a {@link Scheduler} which uses a backing {@link ExecutorService} to schedule + * Runnables for async operators. + *

+ * Prefer using {@link #fromExecutorService(ExecutorService, String)}, + * especially if you plan on using metrics as this gives the executor a meaningful identifier. + * + * @param executorService an {@link ExecutorService} + * + * @return a new {@link Scheduler} + */ + public static Scheduler fromExecutorService(ExecutorService executorService) { + String executorServiceHashcode = Integer.toHexString(System.identityHashCode(executorService)); + return fromExecutorService(executorService, "anonymousExecutor@" + executorServiceHashcode); + } + + /** + * Create a {@link Scheduler} which uses a backing {@link ExecutorService} to schedule + * Runnables for async operators. + * + * @param executorService an {@link ExecutorService} + * + * @return a new {@link Scheduler} + */ + public static Scheduler fromExecutorService(ExecutorService executorService, String executorName) { + final DelegateServiceScheduler scheduler = new DelegateServiceScheduler(executorName, executorService); + scheduler.start(); + 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 + * 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 + * ten times the number of available CPU cores, see {@link #DEFAULT_BOUNDED_ELASTIC_SIZE}). + * The maximum number of task submissions that can be enqueued and deferred on each of these + * backing threads is bounded (by default 100K additional tasks, see + * {@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 + * 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. + *

+ * Note that if a thread is backing a low amount of workers, but these workers submit a lot of pending tasks, + * a second worker could end up being backed by the same thread and see tasks rejected. + * 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. + * + * @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 + */ + 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. + * + * @return default instance of a {@link Scheduler} that hosts a fixed pool of single-threaded + * ExecutorService-based workers and is suited for parallel work + */ + public static Scheduler parallel() { + return cache(CACHED_PARALLEL, PARALLEL, PARALLEL_SUPPLIER); + } + + /** + * Executes tasks immediately instead of scheduling them. + *

+ * As a consequence tasks run on the thread that submitted them (eg. the + * thread on which an operator is currently processing its onNext/onComplete/onError signals). + * This {@link Scheduler} is typically used as a "null object" for APIs that require a + * Scheduler but one doesn't want to change threads. + * + * @return a reusable {@link Scheduler} that executes tasks immediately instead of scheduling them + */ + public static Scheduler immediate() { + return ImmediateScheduler.instance(); + } + + /** + * {@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. + *

+ * The maximum number of created threads is bounded by the provided {@code threadCap}. + * The maximum number of task submissions that can be enqueued and deferred on each of these + * 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 + * 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. + *

+ * Note that if a thread is backing a low amount of workers, but these workers submit a lot of pending tasks, + * a second worker could end up being backed by the same thread and see tasks rejected. + * 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. + *

+ * This scheduler is restartable. Backing threads 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 + * 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) { + return newBoundedElastic(threadCap, queuedTaskCap, name, BoundedElasticScheduler.DEFAULT_TTL_SECONDS, false); + } + + /** + * {@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 the provided {@code ttlSeconds}. + *

+ * The maximum number of created threads is bounded by the provided {@code threadCap}. + * The maximum number of task submissions that can be enqueued and deferred on each of these + * 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 + * 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. + *

+ * Note that if a thread is backing a low amount of workers, but these workers submit a lot of pending tasks, + * a second worker could end up being backed by the same thread and see tasks rejected. + * 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. + *

+ * This scheduler is restartable. Backing threads 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 + * 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) { + return newBoundedElastic(threadCap, queuedTaskCap, name, ttlSeconds, false); + } + + /** + * {@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 the provided {@code ttlSeconds}. + *

+ * The maximum number of created threads is bounded by the provided {@code threadCap}. + * The maximum number of task submissions that can be enqueued and deferred on each of these + * 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 + * 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. + *

+ * Note that if a thread is backing a low amount of workers, but these workers submit a lot of pending tasks, + * a second worker could end up being backed by the same thread and see tasks rejected. + * 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. + *

+ * This scheduler is restartable. Depending on the {@code daemon} parameter, backing threads 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 daemon are backing threads {@link Thread#setDaemon(boolean) daemon threads} + * @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 + */ + 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, + Schedulers::defaultUncaughtException), + 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) + * threads can be evicted if idle for more than the provided {@code ttlSeconds}. + *

+ * The maximum number of created threads is bounded by the provided {@code threadCap}. + * The maximum number of task submissions that can be enqueued and deferred on each of these + * 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 + * 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. + *

+ * Note that if a thread is backing a low amount of workers, but these workers submit a lot of pending tasks, + * a second worker could end up being backed by the same thread and see tasks rejected. + * 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. + *

+ * This scheduler is restartable. Backing threads 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 + * 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, ThreadFactory threadFactory, int ttlSeconds) { + Scheduler fromFactory = factory.newBoundedElastic(threadCap, + queuedTaskCap, + threadFactory, + ttlSeconds); + fromFactory.start(); + return fromFactory; + } + + /** + * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based + * workers and is suited for parallel work. This type of {@link Scheduler} detects and + * rejects usage of blocking Reactor APIs. + * + * @param name Thread prefix + * + * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded + * ExecutorService-based workers and is suited for parallel work + */ + public static Scheduler newParallel(String name) { + return newParallel(name, DEFAULT_POOL_SIZE); + } + + /** + * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based + * workers and is suited for parallel work. This type of {@link Scheduler} detects and + * rejects usage of blocking Reactor APIs. + * + * @param name Thread prefix + * @param parallelism Number of pooled workers. + * + * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded + * ExecutorService-based workers and is suited for parallel work + */ + public static Scheduler newParallel(String name, int parallelism) { + return newParallel(name, parallelism, false); + } + + /** + * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based + * workers and is suited for parallel work. This type of {@link Scheduler} detects and + * rejects usage of blocking Reactor APIs. + * + * @param name Thread prefix + * @param parallelism Number of pooled workers. + * @param daemon false if the {@link Scheduler} requires an explicit {@link + * Scheduler#dispose()} to exit the VM. + * + * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded + * ExecutorService-based workers and is suited for parallel work + */ + public static Scheduler newParallel(String name, int parallelism, boolean daemon) { + return newParallel(parallelism, + new ReactorThreadFactory(name, ParallelScheduler.COUNTER, daemon, + true, Schedulers::defaultUncaughtException)); + } + + /** + * {@link Scheduler} that hosts a fixed pool of single-threaded ExecutorService-based + * workers and is suited for parallel work. + * + * @param parallelism Number of pooled workers. + * @param threadFactory a {@link ThreadFactory} to use for the fixed initialized + * number of {@link Thread} + * + * @return a new {@link Scheduler} that hosts a fixed pool of single-threaded + * ExecutorService-based workers and is suited for parallel work + */ + public static Scheduler newParallel(int parallelism, ThreadFactory threadFactory) { + final Scheduler fromFactory = factory.newParallel(parallelism, threadFactory); + fromFactory.start(); + 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. + * + * @param name Component and thread name prefix + * + * @return a new {@link Scheduler} that hosts a single-threaded ExecutorService-based + * worker + */ + public static Scheduler newSingle(String name) { + return newSingle(name, false); + } + + /** + * {@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. + * + * @param name Component and thread name prefix + * @param daemon false if the {@link Scheduler} requires an explicit {@link + * Scheduler#dispose()} to exit the VM. + * + * @return a new {@link Scheduler} that hosts a single-threaded ExecutorService-based + * worker + */ + public static Scheduler newSingle(String name, boolean daemon) { + return newSingle(new ReactorThreadFactory(name, SingleScheduler.COUNTER, daemon, + true, Schedulers::defaultUncaughtException)); + } + + /** + * {@link Scheduler} that hosts a single-threaded ExecutorService-based worker and is + * suited for parallel work. + * + * @param threadFactory a {@link ThreadFactory} to use for the unique thread of the + * {@link Scheduler} + * + * @return a new {@link Scheduler} that hosts a single-threaded ExecutorService-based + * worker + */ + public static Scheduler newSingle(ThreadFactory threadFactory) { + final Scheduler fromFactory = factory.newSingle(threadFactory); + fromFactory.start(); + return fromFactory; + } + + /** + * Define a hook that is executed 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)}). + * + * @param c the new hook to set. + */ + public static void onHandleError(BiConsumer c) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Hooking new default: onHandleError"); + } + onHandleErrorHook = Objects.requireNonNull(c, "onHandleError"); + } + + /** + * 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}). + * + * @return {@code true} if blocking is forbidden in this thread, {@code false} otherwise + */ + public static boolean isInNonBlockingThread() { + return Thread.currentThread() instanceof NonBlocking; + } + + /** + * Check if calling a Reactor blocking API in the given {@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}). + * + * @return {@code true} if blocking is forbidden in that thread, {@code false} otherwise + */ + public static boolean isNonBlockingThread(Thread t) { + return t instanceof NonBlocking; + } + + /** + * If Micrometer is available, set-up a decorator that will instrument any + * {@link ExecutorService} that backs a {@link Scheduler}. + * No-op if Micrometer isn't available. + * + *

+ * The {@link MeterRegistry} used by reactor can be configured via + * {@link reactor.util.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. + */ + public static void enableMetrics() { + if (Metrics.isInstrumentationAvailable()) { + addExecutorServiceDecorator(SchedulerMetricDecorator.METRICS_DECORATOR_KEY, new SchedulerMetricDecorator()); + } + } + + /** + * If {@link #enableMetrics()} has been previously called, removes the decorator. + * No-op if {@link #enableMetrics()} hasn't been called. + */ + public static void disableMetrics() { + removeExecutorServiceDecorator(SchedulerMetricDecorator.METRICS_DECORATOR_KEY); + } + + /** + * Re-apply default factory to {@link Schedulers} + */ + public static void resetFactory() { + setFactory(DEFAULT); + } + + /** + * Replace {@link Schedulers} factories ({@link #newParallel(String) newParallel}, + * {@link #newSingle(String) newSingle} and {@link #newBoundedElastic(int, int, String) newBoundedElastic}). + * Unlike {@link #setFactory(Factory)}, doesn't shutdown previous Schedulers but + * capture them in a {@link Snapshot} that can be later restored via {@link #resetFrom(Snapshot)}. + *

+ * This method should be called safely and with caution, typically on app startup. + * + * @param newFactory an arbitrary {@link Factory} instance + * @return a {@link Snapshot} representing a restorable snapshot of {@link Schedulers} + */ + public static Snapshot setFactoryWithSnapshot(Factory newFactory) { + //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), + factory); + setFactory(newFactory); + return snapshot; + } + + /** + * 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. + */ + public static void resetFrom(@Nullable Snapshot snapshot) { + if (snapshot == null) { + resetFactory(); + return; + } + //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); + + //From there on, we've restored all the snapshoted instances, the factory can be + //restored too and will start backing Schedulers.newXxx(). + //We thus never create a CachedScheduler by accident. + 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. + */ + public static void resetOnHandleError() { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Reset to factory defaults: onHandleError"); + } + onHandleErrorHook = null; + } + + /** + * 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 + * also use these replacements, re-creating the shared schedulers from the new factory + * upon next use. + *

+ * This method should be called safely and with caution, typically on app startup. + * + * @param factoryInstance an arbitrary {@link Factory} instance. + */ + public static void setFactory(Factory factoryInstance) { + Objects.requireNonNull(factoryInstance, "factoryInstance"); + shutdownNow(); + factory = factoryInstance; + } + + /** + * Set up an additional {@link ScheduledExecutorService} decorator for a given key + * only if that key is not already present. + *

+ * The decorator is a {@link BiFunction} taking the Scheduler and the backing + * {@link ScheduledExecutorService} as second argument. It returns the + * decorated {@link ScheduledExecutorService}. + * + * @param key the key under which to set up the decorator + * @param decorator the executor service decorator to add, if key not already present. + * @return true if the decorator was added, false if a decorator was already present + * for this key. + * @see #setExecutorServiceDecorator(String, BiFunction) + * @see #removeExecutorServiceDecorator(String) + * @see Schedulers#onScheduleHook(String, Function) + */ + public static boolean addExecutorServiceDecorator(String key, BiFunction decorator) { + synchronized (DECORATORS) { + return DECORATORS.putIfAbsent(key, decorator) == null; + } + } + + /** + * Set up an additional {@link ScheduledExecutorService} decorator for a given key, + * even if that key is already present. + *

+ * The decorator is a {@link BiFunction} taking the Scheduler and the backing + * {@link ScheduledExecutorService} as second argument. It returns the + * decorated {@link ScheduledExecutorService}. + * + * @param key the key under which to set up the decorator + * @param decorator the executor service decorator to add, if key not already present. + * @see #addExecutorServiceDecorator(String, BiFunction) + * @see #removeExecutorServiceDecorator(String) + * @see Schedulers#onScheduleHook(String, Function) + */ + public static void setExecutorServiceDecorator(String key, BiFunction decorator) { + synchronized (DECORATORS) { + DECORATORS.put(key, decorator); + } + } + + /** + * Remove an existing {@link ScheduledExecutorService} decorator if it has been set up + * via {@link #addExecutorServiceDecorator(String, BiFunction)}. + *

+ * In case the decorator implements {@link Disposable}, it is also + * {@link Disposable#dispose() disposed}. + * + * @param key the key for the executor service decorator to remove + * @return the removed decorator, or null if none was set for that key + * @see #addExecutorServiceDecorator(String, BiFunction) + * @see #setExecutorServiceDecorator(String, BiFunction) + */ + public static BiFunction removeExecutorServiceDecorator(String key) { + BiFunction removed; + synchronized (DECORATORS) { + removed = DECORATORS.remove(key); + } + if (removed instanceof Disposable) { + ((Disposable) removed).dispose(); + } + return removed; + } + + /** + * This method is aimed at {@link Scheduler} implementors, enabling custom implementations + * that are backed by a {@link ScheduledExecutorService} to also have said executors + * decorated (ie. for instrumentation purposes). + *

+ * It applies the decorators added via + * {@link #addExecutorServiceDecorator(String, BiFunction)}, so it shouldn't be added + * as a decorator. Note also that decorators are not guaranteed to be idempotent, so + * this method should be called only once per executor. + * + * @param owner a {@link Scheduler} that owns the {@link ScheduledExecutorService} + * @param original the {@link ScheduledExecutorService} that the {@link Scheduler} + * wants to use originally + * @return the decorated {@link ScheduledExecutorService}, or the original if no decorator is set up + * @see #addExecutorServiceDecorator(String, BiFunction) + * @see #removeExecutorServiceDecorator(String) + */ + public static ScheduledExecutorService decorateExecutorService(Scheduler owner, ScheduledExecutorService original) { + synchronized (DECORATORS) { + for (BiFunction decorator : DECORATORS.values()) { + original = decorator.apply(owner, original); + } + } + + return original; + } + + /** + * Add or replace a named scheduling {@link Function decorator}. With subsequent calls + * to this method, the onScheduleHook hook can be a composite of several sub-hooks, each + * with a different key. + *

+ * The sub-hook is a {@link Function} taking the scheduled {@link Runnable}. + * It returns the decorated {@link Runnable}. + * + * @param key the key under which to set up the onScheduleHook sub-hook + * @param decorator the {@link Runnable} decorator to add (or replace, if key is already present) + * @see #resetOnScheduleHook(String) + * @see #resetOnScheduleHooks() + */ + public static void onScheduleHook(String key, Function decorator) { + synchronized (onScheduleHooks) { + onScheduleHooks.put(key, decorator); + Function newHook = null; + for (Function function : onScheduleHooks.values()) { + if (newHook == null) { + newHook = function; + } + else { + newHook = newHook.andThen(function); + } + } + onScheduleHook = newHook; + } + } + + /** + * Reset a specific onScheduleHook {@link Function sub-hook} if it has been set up + * via {@link #onScheduleHook(String, Function)}. + * + * @param key the key for onScheduleHook sub-hook to remove + * @see #onScheduleHook(String, Function) + * @see #resetOnScheduleHooks() + */ + public static void resetOnScheduleHook(String key) { + synchronized (onScheduleHooks) { + onScheduleHooks.remove(key); + if (onScheduleHooks.isEmpty()) { + onScheduleHook = Function.identity(); + } + else { + Function newHook = null; + for (Function function : onScheduleHooks.values()) { + if (newHook == null) { + newHook = function; + } + else { + newHook = newHook.andThen(function); + } + } + onScheduleHook = newHook; + } + } + } + + /** + * Remove all onScheduleHook {@link Function sub-hooks}. + * + * @see #onScheduleHook(String, Function) + * @see #resetOnScheduleHook(String) + */ + public static void resetOnScheduleHooks() { + synchronized (onScheduleHooks) { + onScheduleHooks.clear(); + onScheduleHook = null; + } + } + + /** + * Applies the hooks registered with {@link Schedulers#onScheduleHook(String, Function)}. + * + * @param runnable a {@link Runnable} submitted to a {@link Scheduler} + * @return decorated {@link Runnable} if any hook is registered, the original otherwise. + */ + public static Runnable onSchedule(Runnable runnable) { + Function hook = onScheduleHook; + if (hook != null) { + return hook.apply(runnable); + } + else { + return runnable; + } + } + + /** + * 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. + * + * @return default instance of 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} + * 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. + *

+ * 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} + * + * @return a wrapping {@link Scheduler} consistently returning a same worker from a + * source {@link Scheduler} + */ + public static Scheduler single(Scheduler original) { + return new SingleWorkerScheduler(original); + } + + /** + * Public factory hook to override Schedulers behavior globally + */ + 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}. + *

+ * The maximum number of created thread pools is bounded by the provided {@code cap}. + * + * @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 + * the number of backing threads, reuses threads and evict idle ones + */ + default Scheduler newBoundedElastic(int threadCap, int queuedTaskCap, ThreadFactory threadFactory, int ttlSeconds) { + return new BoundedElasticScheduler(threadCap, queuedTaskCap, threadFactory, ttlSeconds); + } + + /** + * {@link Scheduler} that hosts a fixed pool of workers and is suited for parallel + * work. + * + * @param parallelism Number of pooled workers. + * @param threadFactory a {@link ThreadFactory} to use for the fixed initialized + * number of {@link Thread} + * + * @return a new {@link Scheduler} that hosts a fixed pool of workers and is + * suited for parallel work + */ + default Scheduler newParallel(int parallelism, ThreadFactory threadFactory) { + return new ParallelScheduler(parallelism, threadFactory); + } + + /** + * {@link Scheduler} that hosts a single worker and is suited for non-blocking + * work. + * + * @param threadFactory a {@link ThreadFactory} to use for the unique resource of + * the {@link Scheduler} + * + * @return a new {@link Scheduler} that hosts a single worker + */ + default Scheduler newSingle(ThreadFactory threadFactory) { + return new SingleScheduler(threadFactory); + } + } + + /** + * It is also {@link Disposable} in case you don't want to restore the live {@link Schedulers} + */ + public static final class Snapshot implements Disposable { + + @Nullable + final CachedScheduler oldElasticScheduler; + + @Nullable + final CachedScheduler oldBoundedElasticScheduler; + + @Nullable + final CachedScheduler oldParallelScheduler; + + @Nullable + final CachedScheduler oldSingleScheduler; + + final Factory oldFactory; + + private Snapshot(@Nullable CachedScheduler oldElasticScheduler, + @Nullable CachedScheduler oldBoundedElasticScheduler, + @Nullable CachedScheduler oldParallelScheduler, + @Nullable CachedScheduler oldSingleScheduler, + Factory factory) { + this.oldElasticScheduler = oldElasticScheduler; + this.oldBoundedElasticScheduler = oldBoundedElasticScheduler; + this.oldParallelScheduler = oldParallelScheduler; + this.oldSingleScheduler = oldSingleScheduler; + oldFactory = factory; + } + + @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 + static final String IMMEDIATE = "immediate"; + static final String FROM_EXECUTOR = "fromExecutor"; + static final String FROM_EXECUTOR_SERVICE = "fromExecutorService"; + + + // 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); + + static final Supplier PARALLEL_SUPPLIER = + () -> newParallel(PARALLEL, DEFAULT_POOL_SIZE, true); + + static final Supplier SINGLE_SUPPLIER = () -> newSingle(SINGLE, true); + + static final Factory DEFAULT = new Factory() { }; + + static final Map> + DECORATORS = new LinkedHashMap<>(); + + static volatile Factory factory = DEFAULT; + + private static final LinkedHashMap> onScheduleHooks = new LinkedHashMap<>(1); + + @Nullable + private static Function onScheduleHook; + + /** + * Get a {@link CachedScheduler} out of the {@code reference} or create one using the + * {@link Supplier} if the reference is empty, effectively creating a single instance + * to be reused as a default scheduler for the given {@code key} category. + * + * @param reference the cache reference that holds the scheduler + * @param key the "name" for the Scheduler's category/type + * @param supplier the {@link Scheduler} generator to use and wrap into a {@link CachedScheduler}. + * Note that in case of a race, an extraneous Scheduler can be created, but it'll get + * immediately {@link Scheduler#dispose() disposed}. + * @return a {@link CachedScheduler} to be reused, either pre-existing or created + */ + static CachedScheduler cache(AtomicReference reference, String key, Supplier supplier) { + CachedScheduler s = reference.get(); + if (s != null) { + return s; + } + s = new CachedScheduler(key, supplier.get()); + if (reference.compareAndSet(null, s)) { + return s; + } + //the reference was updated in the meantime with a cached scheduler + //fallback to it and dispose the extraneous one + s._dispose(); + return reference.get(); + } + + static final Logger LOGGER = Loggers.getLogger(Schedulers.class); + + static final void defaultUncaughtException(Thread t, Throwable e) { + Schedulers.LOGGER.error("Scheduler worker in group " + t.getThreadGroup().getName() + + " failed with an uncaught exception", e); + } + + static void handleError(Throwable ex) { + Thread thread = Thread.currentThread(); + Throwable t = unwrap(ex); + Thread.UncaughtExceptionHandler x = thread.getUncaughtExceptionHandler(); + if (x != null) { + x.uncaughtException(thread, t); + } + else { + LOGGER.error("Scheduler worker failed with an uncaught exception", t); + } + BiConsumer hook = onHandleErrorHook; + if (hook != null) { + hook.accept(thread, t); + } + } + + static class CachedScheduler implements Scheduler, Supplier, Scannable { + + final Scheduler cached; + final String stringRepresentation; + + CachedScheduler(String key, Scheduler cached) { + this.cached = cached; + this.stringRepresentation = "Schedulers." + key + "()"; + } + + @Override + public Disposable schedule(Runnable task) { + return cached.schedule(task); + } + + @Override + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + return cached.schedule(task, delay, unit); + } + + @Override + public Disposable schedulePeriodically(Runnable task, + long initialDelay, + long period, + TimeUnit unit) { + return cached.schedulePeriodically(task, initialDelay, period, unit); + } + + @Override + public Worker createWorker() { + return cached.createWorker(); + } + + @Override + public long now(TimeUnit unit) { + return cached.now(unit); + } + + @Override + public void start() { + cached.start(); + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return cached.isDisposed(); + } + + @Override + public String toString() { + return stringRepresentation; + } + + @Override + public Object scanUnsafe(Attr key) { + if (Attr.NAME == key) return stringRepresentation; + return Scannable.from(cached).scanUnsafe(key); + } + + /** + * Get the {@link Scheduler} that is cached and wrapped inside this + * {@link CachedScheduler}. + * + * @return the cached Scheduler + */ + @Override + public Scheduler get() { + return cached; + } + + void _dispose() { + cached.dispose(); + } + } + + static Disposable directSchedule(ScheduledExecutorService exec, + Runnable task, + @Nullable Disposable parent, + long delay, + TimeUnit unit) { + task = onSchedule(task); + SchedulerTask sr = new SchedulerTask(task, parent); + Future f; + if (delay <= 0L) { + f = exec.submit((Callable) sr); + } + else { + f = exec.schedule((Callable) sr, delay, unit); + } + sr.setFuture(f); + + return sr; + } + + static Disposable directSchedulePeriodically(ScheduledExecutorService exec, + Runnable task, + long initialDelay, + long period, + TimeUnit unit) { + task = onSchedule(task); + + if (period <= 0L) { + InstantPeriodicWorkerTask isr = + new InstantPeriodicWorkerTask(task, exec); + Future f; + if (initialDelay <= 0L) { + f = exec.submit(isr); + } + else { + f = exec.schedule(isr, initialDelay, unit); + } + isr.setFirst(f); + + return isr; + } + else { + PeriodicSchedulerTask sr = new PeriodicSchedulerTask(task); + Future f = exec.scheduleAtFixedRate(sr, initialDelay, period, unit); + sr.setFuture(f); + + return sr; + } + } + + static Disposable workerSchedule(ScheduledExecutorService exec, + Disposable.Composite tasks, + Runnable task, + long delay, + TimeUnit unit) { + task = onSchedule(task); + + WorkerTask sr = new WorkerTask(task, tasks); + if (!tasks.add(sr)) { + throw Exceptions.failWithRejected(); + } + + try { + Future f; + if (delay <= 0L) { + f = exec.submit((Callable) sr); + } + else { + f = exec.schedule((Callable) sr, delay, unit); + } + sr.setFuture(f); + } + catch (RejectedExecutionException ex) { + sr.dispose(); + //RejectedExecutionException are propagated up + throw ex; + } + + return sr; + } + + static Disposable workerSchedulePeriodically(ScheduledExecutorService exec, + Disposable.Composite tasks, + Runnable task, + long initialDelay, + long period, + TimeUnit unit) { + task = onSchedule(task); + + if (period <= 0L) { + InstantPeriodicWorkerTask isr = + new InstantPeriodicWorkerTask(task, exec, tasks); + if (!tasks.add(isr)) { + throw Exceptions.failWithRejected(); + } + try { + Future f; + if (initialDelay <= 0L) { + f = exec.submit(isr); + } + else { + f = exec.schedule(isr, initialDelay, unit); + } + isr.setFirst(f); + } + catch (RejectedExecutionException ex) { + isr.dispose(); + //RejectedExecutionException are propagated up + throw ex; + } + catch (IllegalArgumentException | NullPointerException ex) { + isr.dispose(); + //IllegalArgumentException are wrapped into RejectedExecutionException and propagated up + throw new RejectedExecutionException(ex); + } + + return isr; + } + + PeriodicWorkerTask sr = new PeriodicWorkerTask(task, tasks); + if (!tasks.add(sr)) { + throw Exceptions.failWithRejected(); + } + + try { + Future f = exec.scheduleAtFixedRate(sr, initialDelay, period, unit); + sr.setFuture(f); + } + catch (RejectedExecutionException ex) { + sr.dispose(); + //RejectedExecutionException are propagated up + throw ex; + } + catch (IllegalArgumentException | NullPointerException ex) { + sr.dispose(); + //IllegalArgumentException are wrapped into RejectedExecutionException and propagated up + throw new RejectedExecutionException(ex); + } + + return sr; + } + + /** + * Scan an {@link Executor} or {@link ExecutorService}, recognizing several special + * implementations. Unwraps some decorating schedulers, recognizes {@link Scannable} + * schedulers and delegates to their {@link Scannable#scanUnsafe(Scannable.Attr)} + * method, introspects {@link ThreadPoolExecutor} instances. + *

+ * If no data can be extracted, defaults to the provided {@code orElse} + * {@link Scannable#scanUnsafe(Scannable.Attr) scanUnsafe}. + * + * @param executor the executor to introspect in a best effort manner. + * @param key the key to scan for. CAPACITY and BUFFERED mainly. + * @return an equivalent of {@link Scannable#scanUnsafe(Scannable.Attr)} but that can + * also work on some implementations of {@link Executor} + */ + @Nullable + static final Object scanExecutor(Executor executor, Scannable.Attr key) { + if (executor instanceof DelegateServiceScheduler.UnsupportedScheduledExecutorService) { + executor = ((DelegateServiceScheduler.UnsupportedScheduledExecutorService) executor).get(); + } + if (executor instanceof Scannable) { + return ((Scannable) executor).scanUnsafe(key); + } + + if (executor instanceof ExecutorService) { + ExecutorService service = (ExecutorService) executor; + if (key == Scannable.Attr.TERMINATED) return service.isTerminated(); + if (key == Scannable.Attr.CANCELLED) return service.isShutdown(); + } + + if (executor instanceof ThreadPoolExecutor) { + final ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) executor; + if (key == Scannable.Attr.CAPACITY) return poolExecutor.getMaximumPoolSize(); + if (key == Scannable.Attr.BUFFERED) return ((Long) (poolExecutor.getTaskCount() - poolExecutor.getCompletedTaskCount())).intValue(); + if (key == Scannable.Attr.LARGE_BUFFERED) return poolExecutor.getTaskCount() - poolExecutor.getCompletedTaskCount(); + } + + return null; + } + +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/SingleScheduler.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/SingleScheduler.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/SingleScheduler.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2016-2021 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.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Supplier; + +import reactor.core.Disposable; +import reactor.core.Scannable; + +/** + * 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 { + + 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(); + } + + SingleScheduler(ThreadFactory factory) { + this.factory = factory; + } + + /** + * Instantiates the default {@link ScheduledExecutorService} for the SingleScheduler + * ({@code Executors.newScheduledThreadPoolExecutor} with core and max pool size of 1). + */ + @Override + public ScheduledExecutorService get() { + ScheduledThreadPoolExecutor e = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1, this.factory); + e.setRemoveOnCancelPolicy(true); + e.setMaximumPoolSize(1); + return e; + } + + @Override + public boolean isDisposed() { + return executor == 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; + } + + if (b == null) { + b = Schedulers.decorateExecutorService(this, this.get()); + } + + if (EXECUTORS.compareAndSet(this, a, b)) { + return; + } + } + } + + @Override + public void dispose() { + ScheduledExecutorService a = executor; + if (a != TERMINATED) { + a = EXECUTORS.getAndSet(this, TERMINATED); + if (a != TERMINATED && a != null) { + a.shutdownNow(); + } + } + } + + @Override + public Disposable schedule(Runnable task) { + 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); + } + + @Override + public Disposable schedulePeriodically(Runnable task, + long initialDelay, + long period, + TimeUnit unit) { + return Schedulers.directSchedulePeriodically(executor, + task, + initialDelay, + period, + unit); + } + + @Override + public String toString() { + StringBuilder ts = new StringBuilder(Schedulers.SINGLE) + .append('('); + if (factory instanceof ReactorThreadFactory) { + ts.append('\"').append(((ReactorThreadFactory) factory).get()).append('\"'); + } + return ts.append(')').toString(); + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + 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); + } + + @Override + public Worker createWorker() { + return new ExecutorServiceWorker(executor); + } + +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/SingleWorkerScheduler.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/SingleWorkerScheduler.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/SingleWorkerScheduler.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2016-2021 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.Executor; +import java.util.concurrent.TimeUnit; + +import reactor.core.Disposable; +import reactor.core.Scannable; + +/** + * Wraps one of the workers of some other Scheduler and + * provides Worker services on top of it. + *

+ * Use the dispose() to release the wrapped worker. + * This scheduler is time-capable if the worker itself is time-capable (can schedule with + * a delay and/or periodically). + */ +final class SingleWorkerScheduler implements Scheduler, Executor, Scannable { + + final Worker main; + + SingleWorkerScheduler(Scheduler actual) { + this.main = actual.createWorker(); + } + + @Override + public void dispose() { + main.dispose(); + } + + @Override + public Disposable schedule(Runnable task) { + return main.schedule(task); + } + + @Override + public Disposable schedule(Runnable task, long delay, TimeUnit unit) { + return main.schedule(task, delay, unit); + } + + @Override + public Disposable schedulePeriodically(Runnable task, long initialDelay, + long period, TimeUnit unit) { + return main.schedulePeriodically(task, initialDelay, period, unit); + } + + @Override + public void execute(Runnable command) { + main.schedule(command); + } + + @Override + public Worker createWorker() { + return new ExecutorScheduler.ExecutorSchedulerWorker(this); + } + + @Override + public boolean isDisposed() { + return main.isDisposed(); + } + + @Override + public String toString() { + Scannable mainScannable = Scannable.from(main); + if (mainScannable.isScanAvailable()) { + return Schedulers.SINGLE + "Worker(" + mainScannable.scanUnsafe(Attr.NAME) + ")"; + } + return Schedulers.SINGLE + "Worker(" + main.toString() + ")"; + } + + @Override + public Object scanUnsafe(Attr key) { + if (key == Attr.TERMINATED || key == Attr.CANCELLED) return isDisposed(); + if (key == Attr.PARENT) return main; + if (key == Attr.NAME) return this.toString(); + + return Scannable.from(main).scanUnsafe(key); + } +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/WorkerTask.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/WorkerTask.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/WorkerTask.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2017-2021 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.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +import reactor.core.Disposable; +import reactor.util.annotation.Nullable; + +/** + * A runnable task for {@link Scheduler} Workers that are time-capable (implementing a + * relevant schedule(delay) and schedulePeriodically(period) methods). + * + * Unlike the one in {@link DelegateServiceScheduler}, this runnable doesn't expose the + * ability to cancel inner task when interrupted. + * + * @author Simon Baslé + * @author David Karnok + */ +final class WorkerTask implements Runnable, Disposable, Callable { + + final Runnable task; + + /** marker that the Worker was disposed and the parent got notified */ + static final Composite DISPOSED = new EmptyCompositeDisposable(); + /** marker that the Worker has completed, for the PARENT field */ + static final Composite DONE = new EmptyCompositeDisposable(); + + + /** marker that the Worker has completed, for the FUTURE field */ + static final Future FINISHED = new FutureTask<>(() -> null); + /** + * marker that the Worker was cancelled from the same thread (ie. within call()/run()), + * which means setFuture might race: we avoid interrupting the Future in this case. + */ + static final Future SYNC_CANCELLED = new FutureTask<>(() -> null); + /** + * marker that the Worker was cancelled from another thread, making it safe to + * interrupt the Future task. + */ + //see https://github.com/reactor/reactor-core/issues/1107 + static final Future ASYNC_CANCELLED = new FutureTask<>(() -> null); + + volatile Future future; + static final AtomicReferenceFieldUpdater FUTURE = + AtomicReferenceFieldUpdater.newUpdater(WorkerTask.class, Future.class, "future"); + + volatile Composite parent; + static final AtomicReferenceFieldUpdater PARENT = + AtomicReferenceFieldUpdater.newUpdater(WorkerTask.class, Composite.class, "parent"); + + volatile Thread thread; + static final AtomicReferenceFieldUpdater THREAD = + AtomicReferenceFieldUpdater.newUpdater(WorkerTask.class, Thread.class, "thread"); + + WorkerTask(Runnable task, Composite parent) { + this.task = task; + PARENT.lazySet(this, parent); + } + + @Override + @Nullable + public Void call() { + THREAD.lazySet(this, Thread.currentThread()); + try { + try { + task.run(); + } + catch (Throwable ex) { + Schedulers.handleError(ex); + } + } + finally { + THREAD.lazySet(this, null); + Composite o = parent; + //note: the o != null check must happen after the compareAndSet for it to always mark task as DONE + if (o != DISPOSED && PARENT.compareAndSet(this, o, DONE) && o != null) { + o.remove(this); + } + + Future f; + for (;;) { + f = future; + if (f == SYNC_CANCELLED || f == ASYNC_CANCELLED || FUTURE.compareAndSet(this, f, FINISHED)) { + break; + } + } + } + return null; + } + + @Override + public void run() { + call(); + } + + void setFuture(Future f) { + for (;;) { + Future o = future; + if (o == FINISHED) { + return; + } + if (o == SYNC_CANCELLED) { + f.cancel(false); + return; + } + if (o == ASYNC_CANCELLED) { + f.cancel(true); + return; + } + if (FUTURE.compareAndSet(this, o, f)) { + return; + } + } + } + + @Override + public boolean isDisposed() { + Composite o = PARENT.get(this); + return o == DISPOSED || o == DONE; + } + + @Override + public void dispose() { + for (;;) { + Future f = future; + if (f == FINISHED || f == SYNC_CANCELLED || f == ASYNC_CANCELLED) { + break; + } + boolean async = thread != Thread.currentThread(); + if (FUTURE.compareAndSet(this, f, async ? ASYNC_CANCELLED : SYNC_CANCELLED)) { + if (f != null) { + f.cancel(async); + } + break; + } + } + + for (;;) { + Composite o = parent; + if (o == DONE || o == DISPOSED || o == null) { + return; + } + if (PARENT.compareAndSet(this, o, DISPOSED)) { + o.remove(this); + return; + } + } + } + +} Index: 3rdParty_sources/reactor/reactor/core/scheduler/package-info.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/core/scheduler/package-info.java (revision 0) +++ 3rdParty_sources/reactor/reactor/core/scheduler/package-info.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +/** + * {@link reactor.core.scheduler.Scheduler} contract and static + * registry and factory methods in {@link reactor.core.scheduler.Schedulers}. + * + * @author Stephane Maldini + */ +@NonNullApi +package reactor.core.scheduler; + +import reactor.util.annotation.NonNullApi; \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/Logger.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/Logger.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/Logger.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2016-2021 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; + +import java.util.function.Supplier; + +/** + * Logger interface designed for internal Reactor usage. + */ +public interface Logger { + + /** + * A kind of {@link java.util.function.Predicate} and {@link Supplier} mix, provides two + * variants of a message {@link String} depending on the level of detail desired. + */ + @FunctionalInterface + interface ChoiceOfMessageSupplier { + + /** + * Provide two possible versions of a message {@link String}, depending on the + * level of detail desired. + * + * @param isVerbose {@code true} for higher level of detail, {@code false} for lower level of detail + * @return the message {@link String} according to the passed level of detail + */ + String get(boolean isVerbose); + } + + /** + * Return the name of this Logger instance. + * @return name of this logger instance + */ + String getName(); + + /** + * Is the logger instance enabled for the TRACE level? + * + * @return True if this Logger is enabled for the TRACE level, + * false otherwise. + */ + boolean isTraceEnabled(); + + /** + * Log a message at the TRACE level. + * + * @param msg the message string to be logged + */ + void trace(String msg); + + /** + * Log a message at the TRACE level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the TRACE level. However, this variant incurs the hidden + * (and relatively small) cost of creating an Object[] before invoking the method, + * even if this logger is disabled for TRACE.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void trace(String format, Object... arguments); + + /** + * Log an exception (throwable) at the TRACE level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void trace(String msg, Throwable t); + + /** + * Is the logger instance enabled for the DEBUG level? + * + * @return True if this Logger is enabled for the DEBUG level, + * false otherwise. + */ + boolean isDebugEnabled(); + + /** + * Log a message at the DEBUG level. + * + * @param msg the message string to be logged + */ + void debug(String msg); + + /** + * Log a message at the DEBUG level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the DEBUG level. However, this variant incurs the hidden + * (and relatively small) cost of creating an Object[] before invoking the method, + * even if this logger is disabled for DEBUG.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void debug(String format, Object... arguments); + + /** + * Log an exception (throwable) at the DEBUG level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void debug(String msg, Throwable t); + + /** + * Is the logger instance enabled for the INFO level? + * + * @return True if this Logger is enabled for the INFO level, + * false otherwise. + */ + boolean isInfoEnabled(); + + /** + * Log a message at the INFO level. + * + * @param msg the message string to be logged + */ + void info(String msg); + + /** + * Log a message at the INFO level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the INFO level. However, this variant incurs the hidden + * (and relatively small) cost of creating an Object[] before invoking the method, + * even if this logger is disabled for INFO.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void info(String format, Object... arguments); + + /** + * Log an exception (throwable) at the INFO level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void info(String msg, Throwable t); + + /** + * Convenience method to log a message that is different according to the log level. + * In priority, DEBUG level is used if {@link #isDebugEnabled()}. + * Otherwise, INFO level is used (unless {@link #isInfoEnabled()} is false). + *

+ * This can be used to log different level of details according to the active + * log level. + * + * @param messageSupplier the {@link ChoiceOfMessageSupplier} invoked in priority + * with {@code true} for the DEBUG level message, or {@code false} for INFO level + * @see #info(String) + */ + default void infoOrDebug(ChoiceOfMessageSupplier messageSupplier) { + if (isDebugEnabled()) { + debug(messageSupplier.get(true)); + } + else if (isInfoEnabled()) { + info(messageSupplier.get(false)); + } + } + + /** + * Convenience method to log an exception (throwable), with an accompanying + * message that is different according to the log level. + * In priority, DEBUG level is used if {@link #isDebugEnabled()}. + * Otherwise, INFO level is used (unless {@link #isInfoEnabled()} is false). + *

+ * This can be used to log different level of details according to the active + * log level. + * + * @param messageSupplier the {@link ChoiceOfMessageSupplier} invoked in priority + * with {@code true} for the DEBUG level message, or {@code false} for INFO level + * @param cause the {@link Throwable} the original exception to be logged + * @see #info(String, Throwable) + */ + default void infoOrDebug(ChoiceOfMessageSupplier messageSupplier, Throwable cause) { + if (isDebugEnabled()) { + debug(messageSupplier.get(true), cause); + } + else if (isInfoEnabled()) { + info(messageSupplier.get(false), cause); + } + } + + /** + * Is the logger instance enabled for the WARN level? + * + * @return True if this Logger is enabled for the WARN level, + * false otherwise. + */ + boolean isWarnEnabled(); + + /** + * Log a message at the WARN level. + * + * @param msg the message string to be logged + */ + void warn(String msg); + + /** + * Log a message at the WARN level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the WARN level. However, this variant incurs the hidden + * (and relatively small) cost of creating an Object[] before invoking the method, + * even if this logger is disabled for WARN.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void warn(String format, Object... arguments); + + /** + * Log an exception (throwable) at the WARN level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void warn(String msg, Throwable t); + + /** + * Convenience method to log a message that is different according to the log level. + * In priority, DEBUG level is used if {@link #isDebugEnabled()}. + * Otherwise, WARN level is used (unless {@link #isWarnEnabled()} is false). + *

+ * This can be used to log different level of details according to the active + * log level. + * + * @param messageSupplier the {@link ChoiceOfMessageSupplier} invoked in priority + * with {@code true} for the DEBUG level message, or {@code false} for WARN level + * @see #warn(String) + */ + default void warnOrDebug(ChoiceOfMessageSupplier messageSupplier) { + if (isDebugEnabled()) { + debug(messageSupplier.get(true)); + } + else if (isWarnEnabled()) { + warn(messageSupplier.get(false)); + } + } + + /** + * Convenience method to log an exception (throwable), with an accompanying + * message that is different according to the log level. + * In priority, DEBUG level is used if {@link #isDebugEnabled()}. + * Otherwise, WARN level is used (unless {@link #isWarnEnabled()} is false). + *

+ * This can be used to log different level of details according to the active + * log level. + * + * @param messageSupplier the {@link ChoiceOfMessageSupplier} invoked in priority + * with {@code true} for the DEBUG level message, or {@code false} for WARN level + * @param cause the {@link Throwable} the original exception to be logged + * @see #warn(String, Throwable) + */ + default void warnOrDebug(ChoiceOfMessageSupplier messageSupplier, Throwable cause) { + if (isDebugEnabled()) { + debug(messageSupplier.get(true), cause); + } + else if (isWarnEnabled()) { + warn(messageSupplier.get(false), cause); + } + } + + /** + * Is the logger instance enabled for the ERROR level? + * + * @return True if this Logger is enabled for the ERROR level, + * false otherwise. + */ + boolean isErrorEnabled(); + + /** + * Log a message at the ERROR level. + * + * @param msg the message string to be logged + */ + void error(String msg); + + /** + * Log a message at the ERROR level according to the specified format + * and arguments. + *

+ *

This form avoids superfluous string concatenation when the logger + * is disabled for the ERROR level. However, this variant incurs the hidden + * (and relatively small) cost of creating an Object[] before invoking the method, + * even if this logger is disabled for ERROR.

+ * + * @param format the format string + * @param arguments a list of 3 or more arguments + */ + void error(String format, Object... arguments); + + /** + * Log an exception (throwable) at the ERROR level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param t the exception (throwable) to log + */ + void error(String msg, Throwable t); + + /** + * Convenience method to log a message that is different according to the log level. + * In priority, DEBUG level is used if {@link #isDebugEnabled()}. + * Otherwise, ERROR level is used (unless {@link #isErrorEnabled()} is false). + *

+ * This can be used to log different level of details according to the active + * log level. + * + * @param messageSupplier the {@link ChoiceOfMessageSupplier} invoked in priority + * with {@code true} for the DEBUG level message, or {@code false} for ERROR level + * @see #error(String) + */ + default void errorOrDebug(ChoiceOfMessageSupplier messageSupplier) { + if (isDebugEnabled()) { + debug(messageSupplier.get(true)); + } + else if (isErrorEnabled()) { + error(messageSupplier.get(false)); + } + } + + /** + * Convenience method to log an exception (throwable), with an accompanying + * message that is different according to the log level. + * In priority, DEBUG level is used if {@link #isDebugEnabled()}. + * Otherwise, ERROR level is used (unless {@link #isErrorEnabled()} is false). + *

+ * This can be used to log different level of details according to the active + * log level. + * + * @param messageSupplier the {@link ChoiceOfMessageSupplier} invoked in priority + * with {@code true} for the DEBUG level message, or {@code false} for ERROR level + * @param cause the {@link Throwable} the original exception to be logged + * @see #error(String, Throwable) + */ + default void errorOrDebug(ChoiceOfMessageSupplier messageSupplier, Throwable cause) { + if (isDebugEnabled()) { + debug(messageSupplier.get(true), cause); + } + else if (isErrorEnabled()) { + error(messageSupplier.get(false), cause); + } + } + +} Index: 3rdParty_sources/reactor/reactor/util/Loggers.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/Loggers.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/Loggers.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,639 @@ +/* + * Copyright (c) 2016-2021 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; + +import java.io.PrintStream; +import java.util.HashMap; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.regex.Matcher; + +import reactor.util.annotation.Nullable; + + +/** + * Expose static methods to get a logger depending on the environment. If SL4J is on the + * classpath, it will be used. Otherwise, there are two possible fallbacks: Console or + * {@link java.util.logging.Logger java.util.logging.Logger}). By default, the Console + * fallback is used. To use the JDK loggers, set the {@value #FALLBACK_PROPERTY} + * {@link System#setProperty(String, String) System property} to "{@code JDK}". + *

+ * One can also force the implementation by using the "useXXX" static methods: + * {@link #useConsoleLoggers()}, {@link #useVerboseConsoleLoggers()}, {@link #useJdkLoggers()} + * and {@link #useSl4jLoggers()} (which may throw an Exception if the library isn't on the + * classpath). Note that the system property method above is preferred, as no cleanup of + * the logger factory initialized at startup is attempted by the useXXX methods. + */ +public abstract class Loggers { + + /** + * The system property that determines which fallback implementation to use for loggers + * when SLF4J isn't available. Use {@code JDK} for the JDK-backed logging and anything + * else for Console-based (the default). + */ + public static final String FALLBACK_PROPERTY = "reactor.logging.fallback"; + + private static Function LOGGER_FACTORY; + + static { + resetLoggerFactory(); + } + + /** + * Attempt to activate the best {@link Logger reactor Logger} factory, by first attempting + * to use the SLF4J one, then falling back to either Console logging or + * {@link java.util.logging.Logger java.util.logging.Logger}). By default, the Console + * fallback is used. To fallback to the JDK loggers, set the {@value #FALLBACK_PROPERTY} + * {@link System#setProperty(String, String) System property} to "{@code JDK}". + * + * @see #useJdkLoggers() + * @see #useConsoleLoggers() + * @see #useVerboseConsoleLoggers() + */ + public static void resetLoggerFactory() { + try { + useSl4jLoggers(); + } + catch (Throwable t) { + if (isFallbackToJdk()) { + useJdkLoggers(); + } + else { + useConsoleLoggers(); + } + } + } + + /** + * Return true if {@link #resetLoggerFactory()} would fallback to java.util.logging + * rather than console-based logging, as defined by the {@link #FALLBACK_PROPERTY} + * System property. + * + * @return true if falling back to JDK, false for Console. + */ + static boolean isFallbackToJdk() { + return "JDK".equalsIgnoreCase(System.getProperty(FALLBACK_PROPERTY)); + } + + /** + * Force the usage of Console-based {@link Logger Loggers}, even if SLF4J is available + * on the classpath. Console loggers will output {@link Logger#error(String) ERROR} and + * {@link Logger#warn(String) WARN} levels to {@link System#err} and levels below to + * {@link System#out}. All levels except TRACE and DEBUG are + * considered enabled. TRACE and DEBUG are omitted. + *

+ * The previously active logger factory is simply replaced without + * any particular clean-up. + */ + public static void useConsoleLoggers() { + String name = Loggers.class.getName(); + Function loggerFactory = new ConsoleLoggerFactory(false); + LOGGER_FACTORY = loggerFactory; + loggerFactory.apply(name).debug("Using Console logging"); + } + + /** + * Force the usage of Console-based {@link Logger Loggers}, even if SLF4J is available + * on the classpath. Console loggers will output {@link Logger#error(String) ERROR} and + * {@link Logger#warn(String) WARN} levels to {@link System#err} and levels below to + * {@link System#out}. All levels (including TRACE and DEBUG) are considered enabled. + *

+ * The previously active logger factory is simply replaced without + * any particular clean-up. + */ + public static void useVerboseConsoleLoggers() { + String name = Loggers.class.getName(); + Function loggerFactory = new ConsoleLoggerFactory(true); + LOGGER_FACTORY = loggerFactory; + loggerFactory.apply(name).debug("Using Verbose Console logging"); + } + + /** + * Use a custom type of {@link Logger} created through the provided {@link Function}, + * which takes a logger name as input. + *

+ * The previously active logger factory is simply replaced without + * any particular clean-up. + * + * @param loggerFactory the {@link Function} that provides a (possibly cached) {@link Logger} + * given a name. + */ + public static void useCustomLoggers(final Function loggerFactory) { + String name = Loggers.class.getName(); + LOGGER_FACTORY = loggerFactory; + loggerFactory.apply(name).debug("Using custom logging"); + } + + /** + * Force the usage of JDK-based {@link Logger Loggers}, even if SLF4J is available + * on the classpath. + *

+ * The previously active logger factory is simply replaced without + * any particular clean-up. + */ + public static void useJdkLoggers() { + String name = Loggers.class.getName(); + Function loggerFactory = new JdkLoggerFactory(); + LOGGER_FACTORY = loggerFactory; + loggerFactory.apply(name).debug("Using JDK logging framework"); + } + + /** + * Force the usage of SL4J-based {@link Logger Loggers}, throwing an exception if + * SLF4J isn't available on the classpath. Prefer using {@link #resetLoggerFactory()} + * as it will fallback in the later case. + *

+ * The previously active logger factory is simply replaced without + * any particular clean-up. + */ + public static void useSl4jLoggers() { + String name = Loggers.class.getName(); + Function loggerFactory = new Slf4JLoggerFactory(); + LOGGER_FACTORY = loggerFactory; + loggerFactory.apply(name).debug("Using Slf4j logging framework"); + } + + /** + * Get a {@link Logger}. + *

+ * For a notion of how the backing implementation is chosen, see + * {@link #resetLoggerFactory()} (or call one of the {@link #useConsoleLoggers() useXxxLoggers} + * methods). + * + * @param name the category or logger name to use + * + * @return a new {@link Logger} instance + */ + public static Logger getLogger(String name) { + return LOGGER_FACTORY.apply(name); + } + + /** + * Get a {@link Logger}, backed by SLF4J if present on the classpath or falling back + * to {@link java.util.logging.Logger java.util.logging.Logger}. + * + * @param cls the source {@link Class} to derive the logger name from. + * + * @return a new {@link Logger} instance + */ + public static Logger getLogger(Class cls) { + return LOGGER_FACTORY.apply(cls.getName()); + } + + private static class Slf4JLoggerFactory implements Function { + + @Override + public Logger apply(String name) { + return new Slf4JLogger(org.slf4j.LoggerFactory.getLogger(name)); + } + } + + private static class Slf4JLogger implements Logger { + + private final org.slf4j.Logger logger; + + public Slf4JLogger(org.slf4j.Logger logger) { + this.logger = logger; + } + + @Override + public String getName() { + return logger.getName(); + } + + @Override + public boolean isTraceEnabled() { + return logger.isTraceEnabled(); + } + + @Override + public void trace(String msg) { + logger.trace(msg); + } + + @Override + public void trace(String format, Object... arguments) { + logger.trace(format, arguments); + } + + @Override + public void trace(String msg, Throwable t) { + logger.trace(msg, t); + } + + @Override + public boolean isDebugEnabled() { + return logger.isDebugEnabled(); + } + + @Override + public void debug(String msg) { + logger.debug(msg); + } + + @Override + public void debug(String format, Object... arguments) { + logger.debug(format, arguments); + } + + @Override + public void debug(String msg, Throwable t) { + logger.debug(msg, t); + } + + @Override + public boolean isInfoEnabled() { + return logger.isInfoEnabled(); + } + + @Override + public void info(String msg) { + logger.info(msg); + } + + @Override + public void info(String format, Object... arguments) { + logger.info(format, arguments); + } + + @Override + public void info(String msg, Throwable t) { + logger.info(msg, t); + } + + @Override + public boolean isWarnEnabled() { + return logger.isWarnEnabled(); + } + + @Override + public void warn(String msg) { + logger.warn(msg); + } + + @Override + public void warn(String format, Object... arguments) { + logger.warn(format, arguments); + } + + @Override + public void warn(String msg, Throwable t) { + logger.warn(msg, t); + } + + @Override + public boolean isErrorEnabled() { + return logger.isErrorEnabled(); + } + + @Override + public void error(String msg) { + logger.error(msg); + } + + @Override + public void error(String format, Object... arguments) { + logger.error(format, arguments); + } + + @Override + public void error(String msg, Throwable t) { + logger.error(msg, t); + } + } + + /** + * Wrapper over JDK logger + */ + static final class JdkLogger implements Logger { + + private final java.util.logging.Logger logger; + + public JdkLogger(java.util.logging.Logger logger) { + this.logger = logger; + } + + @Override + public String getName() { + return logger.getName(); + } + + @Override + public boolean isTraceEnabled() { + return logger.isLoggable(Level.FINEST); + } + + @Override + public void trace(String msg) { + logger.log(Level.FINEST, msg); + } + + @Override + public void trace(String format, Object... arguments) { + logger.log(Level.FINEST, format(format, arguments)); + } + + @Override + public void trace(String msg, Throwable t) { + logger.log(Level.FINEST, msg, t); + } + + @Override + public boolean isDebugEnabled() { + return logger.isLoggable(Level.FINE); + } + + @Override + public void debug(String msg) { + logger.log(Level.FINE, msg); + } + + @Override + public void debug(String format, Object... arguments) { + logger.log(Level.FINE, format(format, arguments)); + } + + @Override + public void debug(String msg, Throwable t) { + logger.log(Level.FINE, msg, t); + } + + @Override + public boolean isInfoEnabled() { + return logger.isLoggable(Level.INFO); + } + + @Override + public void info(String msg) { + logger.log(Level.INFO, msg); + } + + @Override + public void info(String format, Object... arguments) { + logger.log(Level.INFO, format(format, arguments)); + } + + @Override + public void info(String msg, Throwable t) { + logger.log(Level.INFO, msg, t); + } + + @Override + public boolean isWarnEnabled() { + return logger.isLoggable(Level.WARNING); + } + + @Override + public void warn(String msg) { + logger.log(Level.WARNING, msg); + } + + @Override + public void warn(String format, Object... arguments) { + logger.log(Level.WARNING, format(format, arguments)); + } + + @Override + public void warn(String msg, Throwable t) { + logger.log(Level.WARNING, msg, t); + } + + @Override + public boolean isErrorEnabled() { + return logger.isLoggable(Level.SEVERE); + } + + @Override + public void error(String msg) { + logger.log(Level.SEVERE, msg); + } + + @Override + public void error(String format, Object... arguments) { + logger.log(Level.SEVERE, format(format, arguments)); + } + + @Override + public void error(String msg, Throwable t) { + logger.log(Level.SEVERE, msg, t); + } + + @Nullable + final String format(@Nullable String from, @Nullable Object... arguments){ + if(from != null) { + String computed = from; + if (arguments != null && arguments.length != 0) { + for (Object argument : arguments) { + computed = computed.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(argument))); + } + } + return computed; + } + return null; + } + } + + private static class JdkLoggerFactory implements Function { + + @Override + public Logger apply(String name) { + return new JdkLogger(java.util.logging.Logger.getLogger(name)); + } + } + + /** + * A {@link Logger} that has all levels enabled. error and warn log to System.err + * while all other levels log to System.out (printstreams can be changed via constructor). + */ + static final class ConsoleLogger implements Logger { + + private final String name; + private final PrintStream err; + private final PrintStream log; + private final boolean verbose; + + ConsoleLogger(String name, PrintStream log, PrintStream err, boolean verbose) { + this.name = name; + this.log = log; + this.err = err; + this.verbose = verbose; + } + + ConsoleLogger(String name, boolean verbose) { + this(name, System.out, System.err, verbose); + } + + @Override + public String getName() { + return this.name; + } + + @Nullable + final String format(@Nullable String from, @Nullable Object... arguments){ + if(from != null) { + String computed = from; + if (arguments != null && arguments.length != 0) { + for (Object argument : arguments) { + computed = computed.replaceFirst("\\{\\}", Matcher.quoteReplacement(String.valueOf(argument))); + } + } + return computed; + } + return null; + } + + @Override + public boolean isTraceEnabled() { + return verbose; + } + + @Override + public synchronized void trace(String msg) { + if (!verbose) { + return; + } + this.log.format("[TRACE] (%s) %s\n", Thread.currentThread().getName(), msg); + } + + @Override + public synchronized void trace(String format, Object... arguments) { + if (!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) { + return; + } + this.log.format("[TRACE] (%s) %s - %s\n", Thread.currentThread().getName(), msg, t); + t.printStackTrace(this.log); + } + + @Override + public boolean isDebugEnabled() { + return verbose; + } + + @Override + public synchronized void debug(String msg) { + if (!verbose) { + return; + } + this.log.format("[DEBUG] (%s) %s\n", Thread.currentThread().getName(), msg); + } + + @Override + public synchronized void debug(String format, Object... arguments) { + if (!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) { + return; + } + this.log.format("[DEBUG] (%s) %s - %s\n", Thread.currentThread().getName(), msg, t); + t.printStackTrace(this.log); + } + + @Override + public boolean isInfoEnabled() { + return true; + } + + @Override + public synchronized void info(String msg) { + this.log.format("[ INFO] (%s) %s\n", Thread.currentThread().getName(), msg); + } + + @Override + public synchronized void info(String format, Object... arguments) { + this.log.format("[ INFO] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments)); + } + + @Override + public synchronized void info(String msg, Throwable t) { + this.log.format("[ INFO] (%s) %s - %s\n", Thread.currentThread().getName(), msg, t); + t.printStackTrace(this.log); + } + + @Override + public boolean isWarnEnabled() { + return true; + } + + @Override + public synchronized void warn(String msg) { + this.err.format("[ WARN] (%s) %s\n", Thread.currentThread().getName(), msg); + } + + @Override + public synchronized void warn(String format, Object... arguments) { + this.err.format("[ WARN] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments)); + } + + @Override + public synchronized void warn(String msg, Throwable t) { + this.err.format("[ WARN] (%s) %s - %s\n", Thread.currentThread().getName(), msg, t); + t.printStackTrace(this.err); + } + + @Override + public boolean isErrorEnabled() { + return true; + } + + @Override + public synchronized void error(String msg) { + this.err.format("[ERROR] (%s) %s\n", Thread.currentThread().getName(), msg); + } + + @Override + public synchronized void error(String format, Object... arguments) { + this.err.format("[ERROR] (%s) %s\n", Thread.currentThread().getName(), format(format, arguments)); + } + + @Override + public synchronized void error(String msg, Throwable t) { + this.err.format("[ERROR] (%s) %s - %s\n", Thread.currentThread().getName(), msg, t); + t.printStackTrace(this.err); + } + } + + private static final class ConsoleLoggerFactory implements Function { + + private static final HashMap consoleLoggers = new HashMap<>(); + + final boolean verbose; + + private ConsoleLoggerFactory(boolean verbose) { + this.verbose = verbose; + } + + @Override + public Logger apply(String name) { + return consoleLoggers.computeIfAbsent(name, n -> new ConsoleLogger(n, verbose)); + } + } + + Loggers(){} +} Index: 3rdParty_sources/reactor/reactor/util/Metrics.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/Metrics.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/Metrics.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016-2021 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; + +import io.micrometer.core.instrument.MeterRegistry; +import reactor.core.publisher.Flux; + +import static io.micrometer.core.instrument.Metrics.globalRegistry; + +/** + * Utilities around instrumentation and metrics with Micrometer. + * + * @author Simon Baslé + */ +public class Metrics { + + static final boolean isMicrometerAvailable; + + static { + boolean micrometer; + try { + globalRegistry.getRegistries(); + micrometer = true; + } + catch (Throwable t) { + micrometer = false; + } + isMicrometerAvailable = micrometer; + } + + /** + * Check if the current runtime supports metrics / instrumentation, by + * verifying if Micrometer is on the classpath. + * + * @return true if the Micrometer instrumentation facade is available + */ + public static final boolean isInstrumentationAvailable() { + return isMicrometerAvailable; + } + + public static class MicrometerConfiguration { + + private static MeterRegistry registry = globalRegistry; + + /** + * Set the registry to use in reactor for metrics related purposes. + * @return the previously configured registry. + */ + public static MeterRegistry useRegistry(MeterRegistry registry) { + MeterRegistry previous = MicrometerConfiguration.registry; + MicrometerConfiguration.registry = registry; + return previous; + } + + /** + * Get the registry used in reactor for metrics related purposes. + * @see Flux#metrics() + */ + public static MeterRegistry getRegistry() { + return MicrometerConfiguration.registry; + } + } + +} Index: 3rdParty_sources/reactor/reactor/util/annotation/NonNull.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/annotation/NonNull.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/annotation/NonNull.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierNickname; + +/** + * A common Reactor annotation (similar to Spring ones) to declare that annotated elements + * cannot be {@code null}. Leverages JSR 305 meta-annotations to indicate nullability in + * Java to common tools with JSR 305 support and used by Kotlin to infer nullability of + * Reactor API. + * + *

Should be used at parameter, return value, and field level. + * Methods overrides should repeat parent {@code @NonNull} annotations unless they behave + * differently. + * + *

Use {@code @NonNullApi} (scope = parameters + return values) to set the default + * behavior to non-nullable in order to avoid annotating your whole codebase with + * {@code @NonNull}. + * + * @author Sebastien Deleuze + * @author Juergen Hoeller + * @since 3.1.0 + * @see NonNullApi + * @see Nullable + */ +@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Nonnull +@TypeQualifierNickname +public @interface NonNull { +} Index: 3rdParty_sources/reactor/reactor/util/annotation/NonNullApi.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/annotation/NonNullApi.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/annotation/NonNullApi.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; + +/** + * A common Reactor annotation (similar to Spring one) to declare that parameters and return + * values are to be considered as non-nullable by default for a given package. + * Leverages JSR 305 meta-annotations to indicate nullability in Java to common tools with + * JSR 305 support and used by Kotlin to infer nullability of Reactor API. + * + *

Should be used at package level in association with {@link Nullable} + * annotations at parameter and return value level. + * + * @author Sebastien Deleuze + * @author Juergen Hoeller + * @since 3.1.0 + * @see Nullable + * @see NonNull + */ +@Target(ElementType.PACKAGE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Nonnull +@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER}) +public @interface NonNullApi { +} Index: 3rdParty_sources/reactor/reactor/util/annotation/Nullable.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/annotation/Nullable.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/annotation/Nullable.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2017 the original author or authors. + * + * 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierNickname; +import javax.annotation.meta.When; + +/** + * A common Reactor annotation (similar to Spring ones) to declare that annotated elements + * can be {@code null} under some circumstance. Leverages JSR 305 meta-annotations to + * indicate nullability in Java to common tools with JSR 305 support and used by Kotlin to + * infer nullability of Reactor API. + * + *

Should be used at parameter, return value, and field level. + * Methods overrides should repeat parent {@code @Nullable} annotations unless they behave + * differently. + * + *

Can be used in association with {@code NonNullApi} to override the default + * non-nullable semantic to nullable. + * + * @author Sebastien Deleuze + * @author Juergen Hoeller + * @since 3.1.0 + * @see NonNullApi + * @see NonNull + */ +@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Nonnull(when = When.MAYBE) +@TypeQualifierNickname +public @interface Nullable { +} Index: 3rdParty_sources/reactor/reactor/util/concurrent/MpscLinkedQueue.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/concurrent/MpscLinkedQueue.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/concurrent/MpscLinkedQueue.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2018-2021 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.concurrent; + +import java.util.AbstractQueue; +import java.util.Iterator; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.BiPredicate; + +import reactor.util.annotation.Nullable; + +/* + * The code was inspired by the similarly named JCTools class: + * https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/atomic + */ +/** + * A multi-producer single consumer unbounded queue. + * @param the contained value type + */ +final class MpscLinkedQueue extends AbstractQueue implements BiPredicate { + private volatile LinkedQueueNode producerNode; + + private final static AtomicReferenceFieldUpdater PRODUCER_NODE_UPDATER + = AtomicReferenceFieldUpdater.newUpdater(MpscLinkedQueue.class, LinkedQueueNode.class, "producerNode"); + + private volatile LinkedQueueNode consumerNode; + private final static AtomicReferenceFieldUpdater CONSUMER_NODE_UPDATER + = AtomicReferenceFieldUpdater.newUpdater(MpscLinkedQueue.class, LinkedQueueNode.class, "consumerNode"); + + public MpscLinkedQueue() { + LinkedQueueNode node = new LinkedQueueNode<>(); + CONSUMER_NODE_UPDATER.lazySet(this, node); + PRODUCER_NODE_UPDATER.getAndSet(this, node);// this ensures correct construction: + // StoreLoad + } + + + /** + * {@inheritDoc}
+ *

+ * IMPLEMENTATION NOTES:
+ * Offer is allowed from multiple threads.
+ * Offer allocates a new node and: + *

    + *
  1. Swaps it atomically with current producer node (only one producer 'wins') + *
  2. Sets the new node as the node following from the swapped producer node + *
+ * 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) + */ + @Override + @SuppressWarnings("unchecked") + public final boolean offer(final E e) { + Objects.requireNonNull(e, "The offered value 'e' must be non-null"); + + final LinkedQueueNode nextNode = new LinkedQueueNode<>(e); + final LinkedQueueNode prevProducerNode = PRODUCER_NODE_UPDATER.getAndSet(this, nextNode); + // Should a producer thread get interrupted here the chain WILL be broken until that thread is resumed + // and completes the store in prev.next. + prevProducerNode.soNext(nextNode); // StoreStore + return true; + } + + /** + * This is an additional {@link java.util.Queue} extension for + * {@link java.util.Queue#offer} which allows atomically offer two elements at once. + *

+ * IMPLEMENTATION NOTES:
+ * Offer over {@link #test} is allowed from multiple threads.
+ * Offer over {@link #test} allocates a two new nodes and: + *

    + *
  1. Swaps them atomically with current producer node (only one producer 'wins') + *
  2. Sets the new nodes as the node following from the swapped producer node + *
+ * 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) + * + * @param e1 first element to offer + * @param e2 second element to offer + * + * @return indicate whether elements has been successfully offered + */ + @Override + @SuppressWarnings("unchecked") + public boolean test(E e1, E e2) { + Objects.requireNonNull(e1, "The offered value 'e1' must be non-null"); + Objects.requireNonNull(e2, "The offered value 'e2' must be non-null"); + + final LinkedQueueNode nextNode = new LinkedQueueNode<>(e1); + final LinkedQueueNode nextNextNode = new LinkedQueueNode<>(e2); + + final LinkedQueueNode prevProducerNode = PRODUCER_NODE_UPDATER.getAndSet(this, nextNextNode); + // Should a producer thread get interrupted here the chain WILL be broken until that thread is resumed + // and completes the store in prev.next. + nextNode.soNext(nextNextNode); + prevProducerNode.soNext(nextNode); // StoreStore + + return true; + } + + /** + * {@inheritDoc}
+ *

+ * IMPLEMENTATION NOTES:
+ * Poll is allowed from a SINGLE thread.
+ * Poll reads the next node from the consumerNode and: + *

    + *
  1. If it is null, the queue is assumed empty (though it might not be). + *
  2. If it is not null set it as the consumer node and return it's now evacuated value. + *
+ * This means the consumerNode.value is always null, which is also the starting point for the queue. + * Because null values are not allowed to be offered this is the only node with it's value set to null at + * any one time. + * + * @see java.util.Queue#poll() + */ + @Nullable + @Override + public E poll() { + LinkedQueueNode currConsumerNode = consumerNode; // don't load twice, it's alright + LinkedQueueNode nextNode = currConsumerNode.lvNext(); + + if (nextNode != null) + { + // we have to null out the value because we are going to hang on to the node + final E nextValue = nextNode.getAndNullValue(); + + // Fix up the next ref of currConsumerNode to prevent promoted nodes from keeping new ones alive. + // We use a reference to self instead of null because null is already a meaningful value (the next of + // producer node is null). + currConsumerNode.soNext(currConsumerNode); + CONSUMER_NODE_UPDATER.lazySet(this, nextNode); + // currConsumerNode is now no longer referenced and can be collected + return nextValue; + } + else if (currConsumerNode != producerNode) + { + while ((nextNode = currConsumerNode.lvNext()) == null) { } + // got the next node... + // we have to null out the value because we are going to hang on to the node + final E nextValue = nextNode.getAndNullValue(); + + // Fix up the next ref of currConsumerNode to prevent promoted nodes from keeping new ones alive. + // We use a reference to self instead of null because null is already a meaningful value (the next of + // producer node is null). + currConsumerNode.soNext(currConsumerNode); + CONSUMER_NODE_UPDATER.lazySet(this, nextNode); + // currConsumerNode is now no longer referenced and can be collected + return nextValue; + } + return null; + } + + @Nullable + @Override + public E peek() { + LinkedQueueNode currConsumerNode = consumerNode; // don't load twice, it's alright + LinkedQueueNode nextNode = currConsumerNode.lvNext(); + + if (nextNode != null) + { + return nextNode.lpValue(); + } + else if (currConsumerNode != producerNode) + { + while ((nextNode = currConsumerNode.lvNext()) == null) { } + // got the next node... + return nextNode.lpValue(); + } + + return null; + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + + @Override + public void clear() { + while (poll() != null && !isEmpty()) { } // NOPMD + } + + @Override + public int size() { + // Read consumer first, this is important because if the producer is node is 'older' than the consumer + // the consumer may overtake it (consume past it) invalidating the 'snapshot' notion of size. + LinkedQueueNode chaserNode = consumerNode; + LinkedQueueNode producerNode = this.producerNode; + int size = 0; + // must chase the nodes all the way to the producer node, but there's no need to count beyond expected head. + while (chaserNode != producerNode && // don't go passed producer node + chaserNode != null && // stop at last node + size < Integer.MAX_VALUE) // stop at max int + { + LinkedQueueNode next; + next = chaserNode.lvNext(); + // check if this node has been consumed, if so return what we have + if (next == chaserNode) + { + return size; + } + chaserNode = next; + size++; + } + return size; + } + + @Override + public boolean isEmpty() { + return consumerNode == producerNode; + } + + @Override + public Iterator iterator() { + throw new UnsupportedOperationException(); + } + + static final class LinkedQueueNode + { + private volatile LinkedQueueNode next; + private final static AtomicReferenceFieldUpdater NEXT_UPDATER + = AtomicReferenceFieldUpdater.newUpdater(LinkedQueueNode.class, LinkedQueueNode.class, "next"); + + private E value; + + LinkedQueueNode() + { + this(null); + } + + + LinkedQueueNode(@Nullable E val) + { + spValue(val); + } + + /** + * Gets the current value and nulls out the reference to it from this node. + * + * @return value + */ + @Nullable + public E getAndNullValue() + { + E temp = lpValue(); + spValue(null); + return temp; + } + + @Nullable + public E lpValue() + { + return value; + } + + public void spValue(@Nullable E newValue) + { + value = newValue; + } + + public void soNext(@Nullable LinkedQueueNode n) + { + NEXT_UPDATER.lazySet(this, n); + } + + @Nullable + public LinkedQueueNode lvNext() + { + return next; + } + } +} Index: 3rdParty_sources/reactor/reactor/util/concurrent/Queues.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/concurrent/Queues.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/concurrent/Queues.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2017-2021 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.concurrent; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import reactor.core.publisher.Hooks; +import reactor.util.annotation.Nullable; + + +/** + * Queue utilities and suppliers for 1-producer/1-consumer ready queues adapted for + * various given capacities. + */ +public final class Queues { + + public static final int CAPACITY_UNSURE = Integer.MIN_VALUE; + + /** + * Return the capacity of a given {@link Queue} in a best effort fashion. Queues that + * are known to be unbounded will return {@code Integer.MAX_VALUE} and queues that + * have a known bounded capacity will return that capacity. For other {@link Queue} + * implementations not recognized by this method or not providing this kind of + * information, {@link #CAPACITY_UNSURE} ({@code Integer.MIN_VALUE}) is returned. + * + * @param q the {@link Queue} to try to get a capacity for + * @return the capacity of the queue, if discoverable with confidence, or {@link #CAPACITY_UNSURE} negative constant. + */ + public static final int capacity(Queue q) { + if (q instanceof ZeroQueue) { + return 0; + } + if (q instanceof OneQueue) { + return 1; + } + if (q instanceof SpscLinkedArrayQueue) { + return Integer.MAX_VALUE; + } + else if (q instanceof SpscArrayQueue) { + return ((SpscArrayQueue) q).length(); + } + else if(q instanceof MpscLinkedQueue) { + return Integer.MAX_VALUE; + } + else if (q instanceof BlockingQueue) { + return ((BlockingQueue) q).remainingCapacity(); + } + else if (q instanceof ConcurrentLinkedQueue) { + return Integer.MAX_VALUE; + } + else { + return CAPACITY_UNSURE; + } + } + + /** + * An allocation friendly default of available slots in a given container, e.g. slow publishers and or fast/few + * subscribers + */ + public static final int XS_BUFFER_SIZE = Math.max(8, + Integer.parseInt(System.getProperty("reactor.bufferSize.x", "32"))); + /** + * A small default of available slots in a given container, compromise between intensive pipelines, small + * subscribers numbers and memory use. + */ + public static final int SMALL_BUFFER_SIZE = Math.max(16, + Integer.parseInt(System.getProperty("reactor.bufferSize.small", "256"))); + + /** + * Calculate the next power of 2, greater than or equal to x.

From Hacker's Delight, Chapter 3, Harry S. Warren + * Jr. + * + * @param x Value to round up + * + * @return The next power of 2 from x inclusive + */ + public static int ceilingNextPowerOfTwo(final int x) { + return 1 << (32 - Integer.numberOfLeadingZeros(x - 1)); + } + + /** + * + * @param batchSize the bounded or unbounded (int.max) queue size + * @param the reified {@link Queue} generic type + * @return an unbounded or bounded {@link Queue} {@link Supplier} + */ + @SuppressWarnings("unchecked") + public static Supplier> get(int batchSize) { + if (batchSize == Integer.MAX_VALUE) { + return SMALL_UNBOUNDED; + } + if (batchSize == XS_BUFFER_SIZE) { + return XS_SUPPLIER; + } + if (batchSize == SMALL_BUFFER_SIZE) { + return SMALL_SUPPLIER; + } + if (batchSize == 1) { + return ONE_SUPPLIER; + } + if (batchSize == 0) { + return ZERO_SUPPLIER; + } + + final int adjustedBatchSize = Math.max(8, batchSize); + if (adjustedBatchSize > 10_000_000) { + return SMALL_UNBOUNDED; + } + else{ + return () -> Hooks.wrapQueue(new SpscArrayQueue<>(adjustedBatchSize)); + } + } + + /** + * @param x the int to test + * + * @return true if x is a power of 2 + */ + public static boolean isPowerOfTwo(final int x) { + return Integer.bitCount(x) == 1; + } + + /** + * A {@link Supplier} for an empty immutable {@link Queue}, to be used as a placeholder + * in methods that require a Queue when one doesn't expect to store any data in said + * Queue. + * + * @param the reified {@link Queue} generic type + * @return an immutable empty {@link Queue} {@link Supplier} + */ + @SuppressWarnings("unchecked") + public static Supplier> empty() { + return ZERO_SUPPLIER; + } + + /** + * + * @param the reified {@link Queue} generic type + * @return a bounded {@link Queue} {@link Supplier} + */ + @SuppressWarnings("unchecked") + public static Supplier> one() { + return ONE_SUPPLIER; + } + + /** + * @param the reified {@link Queue} generic type + * + * @return a bounded {@link Queue} {@link Supplier} + */ + @SuppressWarnings("unchecked") + public static Supplier> small() { + return SMALL_SUPPLIER; + } + + /** + * + * @param the reified {@link Queue} generic type + * @return an unbounded {@link Queue} {@link Supplier} + */ + @SuppressWarnings("unchecked") + public static Supplier> unbounded() { + return SMALL_UNBOUNDED; + } + + /** + * Returns an unbounded, linked-array-based Queue. Integer.max sized link will + * return the default {@link #SMALL_BUFFER_SIZE} size. + * @param linkSize the link size + * @param the reified {@link Queue} generic type + * @return an unbounded {@link Queue} {@link Supplier} + */ + @SuppressWarnings("unchecked") + public static Supplier> unbounded(int linkSize) { + if (linkSize == XS_BUFFER_SIZE) { + return XS_UNBOUNDED; + } + else if (linkSize == Integer.MAX_VALUE || linkSize == SMALL_BUFFER_SIZE) { + return unbounded(); + } + return () -> Hooks.wrapQueue(new SpscLinkedArrayQueue<>(linkSize)); + } + + /** + * + * @param the reified {@link Queue} generic type + * @return a bounded {@link Queue} {@link Supplier} + */ + @SuppressWarnings("unchecked") + public static Supplier> xs() { + return XS_SUPPLIER; + } + + /** + * Returns an unbounded queue suitable for multi-producer/single-consumer (MPSC) + * scenarios. + * + * @param the reified {@link Queue} generic type + * @return an unbounded MPSC {@link Queue} {@link Supplier} + */ + public static Supplier> unboundedMultiproducer() { + return () -> Hooks.wrapQueue(new MpscLinkedQueue()); + } + + private Queues() { + //prevent construction + } + + static final class OneQueue extends AtomicReference implements Queue { + @Override + public boolean add(T t) { + + while (!offer(t)); + + return true; + } + + @Override + public boolean addAll(Collection c) { + return false; + } + + @Override + public void clear() { + set(null); + } + + @Override + public boolean contains(Object o) { + return Objects.equals(get(), o); + } + + @Override + public boolean containsAll(Collection c) { + return false; + } + + @Override + public T element() { + return get(); + } + + @Override + public boolean isEmpty() { + return get() == null; + } + + @Override + public Iterator iterator() { + return new QueueIterator<>(this); + } + + @Override + public boolean offer(T t) { + if (get() != null) { + return false; + } + lazySet(t); + return true; + } + + @Override + @Nullable + public T peek() { + return get(); + } + + @Override + @Nullable + public T poll() { + T v = get(); + if (v != null) { + lazySet(null); + } + return v; + } + + @Override + public T remove() { + return getAndSet(null); + } + + @Override + public boolean remove(Object o) { + return false; + } + + @Override + public boolean removeAll(Collection c) { + return false; + } + + @Override + public boolean retainAll(Collection c) { + return false; + } + + @Override + public int size() { + return get() == null ? 0 : 1; + } + + @Override + public Object[] toArray() { + T t = get(); + if (t == null) { + return new Object[0]; + } + return new Object[]{t}; + } + + @Override + @SuppressWarnings("unchecked") + public T1[] toArray(T1[] a) { + int size = size(); + if (a.length < size) { + a = (T1[]) java.lang.reflect.Array.newInstance( + a.getClass().getComponentType(), size); + } + if (size == 1) { + a[0] = (T1) get(); + } + if (a.length > size) { + a[size] = null; + } + return a; + } + + private static final long serialVersionUID = -6079491923525372331L; + } + + static final class ZeroQueue implements Queue, Serializable { + + @Override + public boolean add(T t) { + return false; + } + + @Override + public boolean addAll(Collection c) { + return false; + } + + @Override + public void clear() { + //NO-OP + } + + @Override + public boolean contains(Object o) { + return false; + } + + @Override + public boolean containsAll(Collection c) { + return false; + } + + @Override + public T element() { + throw new NoSuchElementException("immutable empty queue"); + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + @Override + public boolean offer(T t) { + return false; + } + + @Override + @Nullable + public T peek() { + return null; + } + + @Override + @Nullable + public T poll() { + return null; + } + + @Override + public T remove() { + throw new NoSuchElementException("immutable empty queue"); + } + + @Override + public boolean remove(Object o) { + return false; + } + + @Override + public boolean removeAll(Collection c) { + return false; + } + + @Override + public boolean retainAll(Collection c) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public Object[] toArray() { + return new Object[0]; + } + + @Override + @SuppressWarnings("unchecked") + public T1[] toArray(T1[] a) { + if (a.length > 0) { + a[0] = null; + } + return a; + } + + private static final long serialVersionUID = -8876883675795156827L; + } + + static final class QueueIterator implements Iterator { + + final Queue queue; + + public QueueIterator(Queue queue) { + this.queue = queue; + } + + @Override + public boolean hasNext() { + return !queue.isEmpty(); + } + + @Override + public T next() { + return queue.poll(); + } + + @Override + public void remove() { + queue.remove(); + } + } + + @SuppressWarnings("rawtypes") + static final Supplier ZERO_SUPPLIER = () -> Hooks.wrapQueue(new ZeroQueue<>()); + @SuppressWarnings("rawtypes") + static final Supplier ONE_SUPPLIER = () -> Hooks.wrapQueue(new OneQueue<>()); + @SuppressWarnings("rawtypes") + static final Supplier XS_SUPPLIER = () -> Hooks.wrapQueue(new SpscArrayQueue<>(XS_BUFFER_SIZE)); + @SuppressWarnings("rawtypes") + static final Supplier SMALL_SUPPLIER = () -> Hooks.wrapQueue(new SpscArrayQueue<>(SMALL_BUFFER_SIZE)); + @SuppressWarnings("rawtypes") + static final Supplier SMALL_UNBOUNDED = + () -> Hooks.wrapQueue(new SpscLinkedArrayQueue<>(SMALL_BUFFER_SIZE)); + @SuppressWarnings("rawtypes") + static final Supplier XS_UNBOUNDED = () -> Hooks.wrapQueue(new SpscLinkedArrayQueue<>(XS_BUFFER_SIZE)); +} Index: 3rdParty_sources/reactor/reactor/util/concurrent/SpscArrayQueue.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/concurrent/SpscArrayQueue.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/concurrent/SpscArrayQueue.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2016-2021 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.concurrent; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceArray; + +import reactor.util.annotation.Nullable; + + +/** + * A bounded, array backed, single-producer single-consumer queue. + * + * This implementation is based on JCTools' SPSC algorithms: + * SpscArrayQueue + * and SpscAtomicArrayQueue + * of which the {@code SpscAtomicArrayQueue} was contributed by one of the authors of this library. The notable difference + * is that this class inlines the AtomicReferenceArray directly and there is no lookahead cache involved; + * item padding has a toll on short lived or bursty uses and lookahead doesn't really matter with small queues. + * + * @param the value type + */ +final class SpscArrayQueue extends SpscArrayQueueP3 implements Queue { + /** */ + private static final long serialVersionUID = 494623116936946976L; + + SpscArrayQueue(int capacity) { + super(Queues.ceilingNextPowerOfTwo(capacity)); + } + + @Override + public boolean offer(T e) { + Objects.requireNonNull(e, "e"); + long pi = producerIndex; + int offset = (int)pi & mask; + if (get(offset) != null) { + return false; + } + lazySet(offset, e); + PRODUCER_INDEX.lazySet(this, pi + 1); + return true; + } + + @Override + @Nullable + public T poll() { + long ci = consumerIndex; + int offset = (int)ci & mask; + + T v = get(offset); + if (v != null) { + lazySet(offset, null); + CONSUMER_INDEX.lazySet(this, ci + 1); + } + return v; + } + + @Override + @Nullable + public T peek() { + int offset = (int)consumerIndex & mask; + return get(offset); + } + + @Override + public boolean isEmpty() { + return producerIndex == consumerIndex; + } + + @Override + public void clear() { + while (poll() != null && !isEmpty()); + } + + @Override + public int size() { + long ci = consumerIndex; + for (;;) { + long pi = producerIndex; + long ci2 = consumerIndex; + if (ci == ci2) { + return (int)(pi - ci); + } + ci = ci2; + } + } + + @Override + public boolean contains(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + throw new UnsupportedOperationException(); + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public R[] toArray(R[] a) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(T e) { + throw new UnsupportedOperationException(); + } + + @Override + public T remove() { + throw new UnsupportedOperationException(); + } + + @Override + public T element() { + throw new UnsupportedOperationException(); + } +} + +class SpscArrayQueueCold extends AtomicReferenceArray { + /** */ + private static final long serialVersionUID = 8491797459632447132L; + + final int mask; + + public SpscArrayQueueCold(int length) { + super(length); + mask = length - 1; + } +} +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; + + SpscArrayQueueP1(int length) { + super(length); + } +} + +class SpscArrayQueueProducer extends SpscArrayQueueP1 { + + /** */ + private static final long serialVersionUID = 1657408315616277653L; + + SpscArrayQueueProducer(int length) { + super(length); + } + + volatile long producerIndex; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater PRODUCER_INDEX = + AtomicLongFieldUpdater.newUpdater(SpscArrayQueueProducer.class, "producerIndex"); + +} + +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; + + SpscArrayQueueP2(int length) { + super(length); + } +} + +class SpscArrayQueueConsumer extends SpscArrayQueueP2 { + + /** */ + private static final long serialVersionUID = 4075549732218321659L; + + SpscArrayQueueConsumer(int length) { + super(length); + } + + volatile long consumerIndex; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater CONSUMER_INDEX = + AtomicLongFieldUpdater.newUpdater(SpscArrayQueueConsumer.class, "consumerIndex"); + +} + +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; + + SpscArrayQueueP3(int length) { + super(length); + } +} Index: 3rdParty_sources/reactor/reactor/util/concurrent/SpscLinkedArrayQueue.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/concurrent/SpscLinkedArrayQueue.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/concurrent/SpscLinkedArrayQueue.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2016-2021 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.concurrent; + +import java.util.AbstractQueue; +import java.util.Iterator; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.function.BiPredicate; + +import reactor.util.annotation.Nullable; + + +/** + * An unbounded, array-backed single-producer, single-consumer queue with a fixed link + * size. + *

+ * This implementation is based on JCTools' SPSC algorithms: SpscUnboundedArrayQueue + * and SpscUnboundedAtomicArrayQueue + * of which the {@code SpscUnboundedAtomicArrayQueue} was contributed by one of the + * authors of this library. The notable difference is that this class is not padded and + * there is no lookahead cache involved; padding has a toll on short lived or bursty uses + * and lookahead doesn't really matter with small queues. + * + * @param the value type + */ +final class SpscLinkedArrayQueue extends AbstractQueue + implements BiPredicate { + + final int mask; + + volatile long producerIndex; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater PRODUCER_INDEX = + AtomicLongFieldUpdater.newUpdater(SpscLinkedArrayQueue.class, + "producerIndex"); + AtomicReferenceArray producerArray; + + volatile long consumerIndex; + @SuppressWarnings("rawtypes") + static final AtomicLongFieldUpdater CONSUMER_INDEX = + AtomicLongFieldUpdater.newUpdater(SpscLinkedArrayQueue.class, + "consumerIndex"); + AtomicReferenceArray consumerArray; + + static final Object NEXT = new Object(); + + SpscLinkedArrayQueue(int linkSize) { + int c = Queues.ceilingNextPowerOfTwo(Math.max(8, linkSize)); + this.producerArray = this.consumerArray = new AtomicReferenceArray<>(c + 1); + this.mask = c - 1; + } + + @Override + public boolean offer(T e) { + Objects.requireNonNull(e); + + long pi = producerIndex; + AtomicReferenceArray a = producerArray; + int m = mask; + + int offset = (int) (pi + 1) & m; + + if (a.get(offset) != null) { + offset = (int) pi & m; + + AtomicReferenceArray b = new AtomicReferenceArray<>(m + 2); + producerArray = b; + b.lazySet(offset, e); + a.lazySet(m + 1, b); + a.lazySet(offset, NEXT); + PRODUCER_INDEX.lazySet(this, pi + 1); + } + else { + offset = (int) pi & m; + a.lazySet(offset, e); + PRODUCER_INDEX.lazySet(this, pi + 1); + } + + return true; + } + + /** + * Offer two elements at the same time. + *

Don't use the regular offer() with this at all! + * + * @param first the first value, not null + * @param second the second value, not null + * + * @return true if the queue accepted the two new values + */ + @Override + public boolean test(T first, T second) { + final AtomicReferenceArray buffer = producerArray; + final long p = producerIndex; + final int m = mask; + + int pi = (int) (p + 2) & m; + + if (null != buffer.get(pi)) { + final AtomicReferenceArray newBuffer = + new AtomicReferenceArray<>(m + 2); + producerArray = newBuffer; + + pi = (int) p & m; + newBuffer.lazySet(pi + 1, second);// StoreStore + newBuffer.lazySet(pi, first); + buffer.lazySet(buffer.length() - 1, newBuffer); + + buffer.lazySet(pi, NEXT); // new buffer is visible after element is + + PRODUCER_INDEX.lazySet(this, p + 2);// this ensures correctness on 32bit + // platforms + } + else { + pi = (int) p & m; + buffer.lazySet(pi + 1, second); + buffer.lazySet(pi, first); + PRODUCER_INDEX.lazySet(this, p + 2); + } + + return true; + } + + @SuppressWarnings("unchecked") + @Override + @Nullable + public T poll() { + long ci = consumerIndex; + AtomicReferenceArray a = consumerArray; + int m = mask; + + int offset = (int) ci & m; + + Object o = a.get(offset); + + if (o == null) { + return null; + } + if (o == NEXT) { + AtomicReferenceArray b = (AtomicReferenceArray) a.get(m + 1); + a.lazySet(m + 1, null); + o = b.get(offset); + a = b; + consumerArray = b; + } + a.lazySet(offset, null); + CONSUMER_INDEX.lazySet(this, ci + 1); + + return (T) o; + } + + @SuppressWarnings("unchecked") + @Override + @Nullable + public T peek() { + long ci = consumerIndex; + AtomicReferenceArray a = consumerArray; + int m = mask; + + int offset = (int) ci & m; + + Object o = a.get(offset); + + if (o == null) { + return null; + } + if (o == NEXT) { + a = (AtomicReferenceArray) a.get(m + 1); + o = a.get(offset); + } + + return (T) o; + } + + @Override + public boolean isEmpty() { + return producerIndex == consumerIndex; + } + + @Override + public int size() { + long ci = consumerIndex; + for (; ; ) { + long pi = producerIndex; + long ci2 = consumerIndex; + if (ci == ci2) { + return (int) (pi - ci); + } + ci = ci2; + } + } + + @Override + public void clear() { + while (poll() != null && !isEmpty()) { + } + } + + @Override + public Iterator iterator() { + throw new UnsupportedOperationException(); + } +} Index: 3rdParty_sources/reactor/reactor/util/concurrent/package-info.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/concurrent/package-info.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/concurrent/package-info.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +/** + * Queue {@link reactor.util.concurrent.Queues suppliers and utilities} + * Used for operational serialization (serializing threads) or buffering (asynchronous boundary). + * + * @author Stephane Maldini + */ +@NonNullApi +package reactor.util.concurrent; + +import reactor.util.annotation.NonNullApi; \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/context/Context.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/Context.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/Context.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2017-2021 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.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; + +import reactor.util.annotation.Nullable; + +/** + * A key/value store that is propagated between components such as operators via the + * context protocol. Contexts are ideal to transport orthogonal information such as + * tracing or security tokens. + *

+ * {@link Context} implementations are thread-safe and immutable: mutative operations like + * {@link #put(Object, Object)} will in fact return a new {@link Context} instance. + *

+ * Note that contexts are optimized for low cardinality key/value storage, and a user + * 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}. + * + * @author Stephane Maldini + */ +public interface Context extends ContextView { + + /** + * Return an empty {@link Context} + * + * @return an empty {@link Context} + */ + static Context empty() { + return Context0.INSTANCE; + } + + /** + * Create a {@link Context} pre-initialized with one key-value pair. + * + * @param key the key to initialize. + * @param value the value for the key. + * @return a {@link Context} with a single entry. + * @throws NullPointerException if either key or value are null + */ + static Context of(Object key, Object value) { + return new Context1(key, value); + } + + /** + * Create a {@link Context} pre-initialized with two key-value pairs. + * + * @param key1 the first key to initialize. + * @param value1 the value for the first key. + * @param key2 the second key to initialize. + * @param value2 the value for the second key. + * @return a {@link Context} with two entries. + * @throws NullPointerException if any key or value is null + */ + static Context of(Object key1, Object value1, + Object key2, Object value2) { + return new Context2(key1, value1, key2, value2); + } + + /** + * Create a {@link Context} pre-initialized with three key-value pairs. + * + * @param key1 the first key to initialize. + * @param value1 the value for the first key. + * @param key2 the second key to initialize. + * @param value2 the value for the second key. + * @param key3 the third key to initialize. + * @param value3 the value for the third key. + * @return a {@link Context} with three entries. + * @throws NullPointerException if any key or value is null + */ + static Context of(Object key1, Object value1, + Object key2, Object value2, + Object key3, Object value3) { + return new Context3(key1, value1, key2, value2, key3, value3); + } + + /** + * Create a {@link Context} pre-initialized with four key-value pairs. + * + * @param key1 the first key to initialize. + * @param value1 the value for the first key. + * @param key2 the second key to initialize. + * @param value2 the value for the second key. + * @param key3 the third key to initialize. + * @param value3 the value for the third key. + * @param key4 the fourth key to initialize. + * @param value4 the value for the fourth key. + * @return a {@link Context} with four entries. + * @throws NullPointerException if any key or value is null + */ + static Context of(Object key1, Object value1, + Object key2, Object value2, + Object key3, Object value3, + Object key4, Object value4) { + return new Context4(key1, value1, key2, value2, key3, value3, key4, value4); + } + + /** + * Create a {@link Context} pre-initialized with five key-value pairs. + * + * @param key1 the first key to initialize. + * @param value1 the value for the first key. + * @param key2 the second key to initialize. + * @param value2 the value for the second key. + * @param key3 the third key to initialize. + * @param value3 the value for the third key. + * @param key4 the fourth key to initialize. + * @param value4 the value for the fourth key. + * @param key5 the fifth key to initialize. + * @param value5 the value for the fifth key. + * @return a {@link Context} with five entries. + * @throws NullPointerException if any key or value is null + */ + static Context of(Object key1, Object value1, + Object key2, Object value2, + Object key3, Object value3, + Object key4, Object value4, + Object key5, Object value5) { + return new Context5(key1, value1, key2, value2, key3, value3, key4, value4, key5, value5); + } + + /** + * Create a {@link Context} out of a {@link Map}. Prefer this method if you're somehow + * incapable of checking keys are all distinct in other {@link #of(Object, Object, Object, Object, Object, Object, Object, Object)} + * implementations. + * + * @implNote this method compacts smaller maps into a relevant fields-based implementation + * when map size is less than 6. + */ + static Context of(Map map) { + int size = Objects.requireNonNull(map, "map").size(); + if (size == 0) return Context.empty(); + if (size <= 5) { + Map.Entry[] entries = map.entrySet().toArray(new Map.Entry[size]); + switch (size) { + case 1: + return new Context1(entries[0].getKey(), entries[0].getValue()); + case 2: + return new Context2(entries[0].getKey(), entries[0].getValue(), + entries[1].getKey(), entries[1].getValue()); + case 3: + return new Context3(entries[0].getKey(), entries[0].getValue(), + entries[1].getKey(), entries[1].getValue(), + entries[2].getKey(), entries[2].getValue()); + case 4: + return new Context4(entries[0].getKey(), entries[0].getValue(), + entries[1].getKey(), entries[1].getValue(), + entries[2].getKey(), entries[2].getValue(), + entries[3].getKey(), entries[3].getValue()); + case 5: + return new Context5(entries[0].getKey(), entries[0].getValue(), + entries[1].getKey(), entries[1].getValue(), + entries[2].getKey(), entries[2].getValue(), + entries[3].getKey(), entries[3].getValue(), + entries[4].getKey(), entries[4].getValue()); + } + } + // Since ContextN(Map) is a low level API that DOES NOT perform null checks, + // we need to check every key/value before passing it to ContextN(Map) + map.forEach((key, value) -> { + Objects.requireNonNull(key, "null key found"); + if (value == null) { + throw new NullPointerException("null value for key " + key); + } + }); + @SuppressWarnings("unchecked") + final Map generifiedMap = (Map) map; + return new ContextN(generifiedMap); + } + + /** + * Create a {@link Context} out of a {@link ContextView}, enabling write API on top of + * the read-only view. If the {@link ContextView} is already a {@link Context}, return + * the same instance. + * + * @param contextView the {@link ContextView} to convert (or cast) to {@link Context} + * @return the converted {@link Context} for further modifications + */ + static Context of(ContextView contextView) { + Objects.requireNonNull(contextView, "contextView"); + if (contextView instanceof Context) { + return (Context) contextView; + } + return Context.empty().putAll(contextView); + } + + /** + * Switch to the {@link ContextView} interface, which only allows reading from the + * context. + * @return the {@link ContextView} of this context + */ + default ContextView readOnly() { + return this; + } + + /** + * Create a new {@link Context} that contains all current key/value pairs plus the + * given key/value pair. If that key existed in the current Context, its associated + * value is replaced in the resulting {@link Context}. + * + * @param key the key to add/update in the new {@link Context} + * @param value the value to associate to the key in the new {@link Context} + * + * @return a new {@link Context} including the provided key/value + * @throws NullPointerException if either the key or value are null + */ + Context put(Object key, Object value); + + /** + * Create a new {@link Context} that contains all current key/value pairs plus the + * given key/value pair only if the value is not {@literal null}. If that key existed in the + * current Context, its associated value is replaced in the resulting {@link Context}. + * + * @param key the key to add/update in the new {@link Context} + * @param valueOrNull the value to associate to the key in the new {@link Context}, null to ignore the operation + * + * @return a new {@link Context} including the provided key/value, or the same {@link Context} if value is null + * @throws NullPointerException if the key is null + */ + default Context putNonNull(Object key, @Nullable Object valueOrNull) { + if (valueOrNull != null) { + return put(key, valueOrNull); + } + return this; + } + + /** + * Return a new {@link Context} that will resolve all existing keys except the + * removed one, {@code key}. + *

+ * Note that if this {@link Context} doesn't contain the key, this method simply + * returns this same instance. + * + * @param key the key to remove. + * @return a new {@link Context} that doesn't include the provided key + */ + Context delete(Object key); + + /** + * Create a new {@link Context} by merging the content of this context and a given + * {@link ContextView}. If the other context is empty, the same {@link Context} instance + * is returned. + * + * @param other the other {@link ContextView} from which to copy entries + * @return a new {@link Context} with a merge of the entries from this context and the given context. + */ + default Context putAll(ContextView other) { + if (other.isEmpty()) return this; + + if (other instanceof CoreContext) { + CoreContext coreContext = (CoreContext) other; + return coreContext.putAllInto(this); + } + + ContextN newContext = new ContextN(this.size() + other.size()); + this.stream().sequential().forEach(newContext); + other.stream().sequential().forEach(newContext); + if (newContext.size() <= 5) { + // make it return Context{1-5} + return Context.of((Map) newContext); + } + return newContext; + } + + /** + * See {@link #putAll(ContextView)}. + * + * @deprecated will be removed in 3.5, kept for backward compatibility with 3.3. Until + * then if you need to work around the deprecation, use {@link #putAll(ContextView)} + * combined with {@link #readOnly()} + * @param context the {@link Context} from which to copy entries + * @return a new {@link Context} with a merge of the entries from this context and the given context. + */ + @Deprecated + default Context putAll(Context context) { + return this.putAll(context.readOnly()); + } +} Index: 3rdParty_sources/reactor/reactor/util/context/Context0.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/Context0.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/Context0.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2015-2021 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.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Stream; + +final class Context0 implements CoreContext { + + static final Context0 INSTANCE = new Context0(); + + @Override + public Context put(Object key, Object value) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(value, "value"); + return new Context1(key, value); + } + + @Override + public Context delete(Object key) { + return this; + } + + @Override + public T get(Object key) { + throw new NoSuchElementException("Context is empty"); + } + + @Override + public boolean hasKey(Object key) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public String toString() { + return "Context0{}"; + } + + @Override + public Stream> stream() { + return Stream.empty(); + } + + @Override + public Context putAllInto(Context base) { + return base; + } + + @Override + public void unsafePutAllInto(ContextN other) { + } +} Index: 3rdParty_sources/reactor/reactor/util/context/Context1.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/Context1.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/Context1.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2015-2021 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.AbstractMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Stream; + +final class Context1 implements CoreContext { + + final Object key; + final Object value; + + Context1(Object key, Object value) { + this.key = Objects.requireNonNull(key, "key"); + this.value = Objects.requireNonNull(value, "value"); + } + + @Override + public Context put(Object key, Object value) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(value, "value"); + + if(this.key.equals(key)){ + return new Context1(key, value); + } + + return new Context2(this.key, this.value, key, value); + } + + @Override + public Context delete(Object key) { + Objects.requireNonNull(key, "key"); + if (this.key.equals(key)) { + return Context.empty(); + } + return this; + } + + @Override + public boolean hasKey(Object key) { + return this.key.equals(key); + } + + @Override + @SuppressWarnings("unchecked") + public T get(Object key) { + if (hasKey(key)) { + return (T)this.value; + } + throw new NoSuchElementException("Context does not contain key: " + key); + } + + @Override + public Stream> stream() { + return Stream.of(new AbstractMap.SimpleImmutableEntry<>(key, value)); + } + + @Override + public Context putAllInto(Context base) { + return base.put(key, value); + } + + @Override + public void unsafePutAllInto(ContextN other) { + other.accept(key, value); + } + + @Override + public int size() { + return 1; + } + + @Override + public String toString() { + return "Context1{" + key + '='+ value + '}'; + } +} Index: 3rdParty_sources/reactor/reactor/util/context/Context2.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/Context2.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/Context2.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015-2021 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.AbstractMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Stream; + +final class Context2 implements CoreContext { + + final Object key1; + final Object value1; + final Object key2; + final Object value2; + + Context2(Object key1, Object value1, Object key2, Object value2) { + if (Objects.requireNonNull(key1, "key1").equals(key2)) { + throw new IllegalArgumentException("Key #1 (" + key1 + ") is duplicated"); + } + this.key1 = key1; //checked for nulls above + this.value1 = Objects.requireNonNull(value1, "value1"); + this.key2 = Objects.requireNonNull(key2, "key2"); + this.value2 = Objects.requireNonNull(value2, "value2"); + } + + @Override + public Context put(Object key, Object value) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(value, "value"); + + if(this.key1.equals(key)){ + return new Context2(key, value, key2, value2); + } + + if (this.key2.equals(key)) { + return new Context2(key1, value1, key, value); + } + + return new Context3(this.key1, this.value1, this.key2, this.value2, key, value); + } + + @Override + public Context delete(Object key) { + Objects.requireNonNull(key, "key"); + + if(this.key1.equals(key)){ + return new Context1(key2, value2); + } + + if (this.key2.equals(key)) { + return new Context1(key1, value1); + } + + return this; + } + + @Override + public boolean hasKey(Object key) { + return this.key1.equals(key) || this.key2.equals(key); + } + + @Override + @SuppressWarnings("unchecked") + public T get(Object key) { + if (this.key1.equals(key)) { + return (T)this.value1; + } + if (this.key2.equals(key)) { + return (T)this.value2; + } + throw new NoSuchElementException("Context does not contain key: "+key); + } + + @Override + public int size() { + return 2; + } + + @Override + public Stream> stream() { + return Stream.of( + new AbstractMap.SimpleImmutableEntry<>(key1, value1), + new AbstractMap.SimpleImmutableEntry<>(key2, value2)); + } + + @Override + public Context putAllInto(Context base) { + return base + .put(this.key1, this.value1) + .put(this.key2, this.value2); + } + + @Override + public void unsafePutAllInto(ContextN other) { + other.accept(key1, value1); + other.accept(key2, value2); + } + + @Override + public String toString() { + return "Context2{" + key1 + '='+ value1 + ", " + key2 + '=' + value2 + '}'; + } +} Index: 3rdParty_sources/reactor/reactor/util/context/Context3.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/Context3.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/Context3.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2015-2021 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.AbstractMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Stream; + +final class Context3 implements CoreContext { + + final Object key1; + final Object value1; + final Object key2; + final Object value2; + final Object key3; + final Object value3; + + Context3(Object key1, Object value1, + Object key2, Object value2, + Object key3, Object value3) { + if (Objects.requireNonNull(key1, "key1").equals(key2) || key1.equals(key3)) { + throw new IllegalArgumentException("Key #1 (" + key1 + ") is duplicated"); + } + else if (Objects.requireNonNull(key2, "key2").equals(key3)) { + throw new IllegalArgumentException("Key #2 (" + key2 + ") is duplicated"); + } + this.key1 = key1; //already checked for null above + this.value1 = Objects.requireNonNull(value1, "value1"); + this.key2 = key2; //already checked for null above + this.value2 = Objects.requireNonNull(value2, "value2"); + this.key3 = Objects.requireNonNull(key3, "key3"); + this.value3 = Objects.requireNonNull(value3, "value3"); + } + + @Override + public Context put(Object key, Object value) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(value, "value"); + + if(this.key1.equals(key)){ + return new Context3(key, value, key2, value2, key3, value3); + } + + if (this.key2.equals(key)) { + return new Context3(key1, value1, key, value, key3, value3); + } + + if (this.key3.equals(key)) { + return new Context3(key1, value1, key2, value2, key, value); + } + + return new Context4(this.key1, this.value1, this.key2, this.value2, this.key3, this.value3, key, value); + } + + @Override + public Context delete(Object key) { + Objects.requireNonNull(key, "key"); + + if(this.key1.equals(key)){ + return new Context2(key2, value2, key3, value3); + } + + if (this.key2.equals(key)) { + return new Context2(key1, value1, key3, value3); + } + + if (this.key3.equals(key)) { + return new Context2(key1, value1, key2, value2); + } + + return this; + } + + @Override + public boolean hasKey(Object key) { + return this.key1.equals(key) || this.key2.equals(key) || this.key3.equals(key); + } + + @Override + @SuppressWarnings("unchecked") + public T get(Object key) { + if (this.key1.equals(key)) { + return (T)this.value1; + } + if (this.key2.equals(key)) { + return (T)this.value2; + } + if (this.key3.equals(key)) { + return (T)this.value3; + } + throw new NoSuchElementException("Context does not contain key: "+key); + } + + @Override + public int size() { + return 3; + } + + @Override + public Stream> stream() { + return Stream.of( + new AbstractMap.SimpleImmutableEntry<>(key1, value1), + new AbstractMap.SimpleImmutableEntry<>(key2, value2), + new AbstractMap.SimpleImmutableEntry<>(key3, value3)); + } + + @Override + public Context putAllInto(Context base) { + return base + .put(this.key1, this.value1) + .put(this.key2, this.value2) + .put(this.key3, this.value3); + } + + @Override + public void unsafePutAllInto(ContextN other) { + other.accept(key1, value1); + other.accept(key2, value2); + other.accept(key3, value3); + } + + @Override + public String toString() { + return "Context3{" + key1 + '='+ value1 + ", " + key2 + '=' + value2 + ", " + key3 + '=' + value3 + '}'; + } +} Index: 3rdParty_sources/reactor/reactor/util/context/Context4.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/Context4.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/Context4.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2017-2021 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.AbstractMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Stream; + +final class Context4 implements CoreContext { + + /** + * Checks for duplicate keys and null keys. This method is intended for a short space of keys in the + * 4-10 range. Shorter number of keys can easily be checked with direct equals comparison(s), + * saving on allocating a vararg array (although this method would still behave correctly). + * + * @param keys the keys to check for duplicates and nulls, by looping over the combinations + * @throws NullPointerException if any of the keys is null + * @throws IllegalArgumentException on the first key encountered twice + */ + static void checkKeys(Object... keys) { + int size = keys.length; + //NB: there is no sense in looking for duplicates when size < 2, but the loop below skips these cases anyway + for (int i = 0; i < size - 1; i++) { + Object key = Objects.requireNonNull(keys[i], "key" + (i+1)); + for (int j = i + 1; j < size; j++) { + Object otherKey = keys[j]; + if (key.equals(otherKey)) { + throw new IllegalArgumentException("Key #" + (i+1) + " (" + key + ") is duplicated"); + } + } + } + //at the end of the loops, only the last key hasn't been checked for null + if (size != 0) { + Objects.requireNonNull(keys[size - 1], "key" + size); + } + } + + final Object key1; + final Object value1; + final Object key2; + final Object value2; + final Object key3; + final Object value3; + final Object key4; + final Object value4; + + Context4(Object key1, Object value1, + Object key2, Object value2, + Object key3, Object value3, + Object key4, Object value4) { + //TODO merge null check and duplicate check in the util method + Context4.checkKeys(key1, key2, key3, key4); + this.key1 = Objects.requireNonNull(key1, "key1"); + this.value1 = Objects.requireNonNull(value1, "value1"); + this.key2 = Objects.requireNonNull(key2, "key2"); + this.value2 = Objects.requireNonNull(value2, "value2"); + this.key3 = Objects.requireNonNull(key3, "key3"); + this.value3 = Objects.requireNonNull(value3, "value3"); + this.key4 = Objects.requireNonNull(key4, "key4"); + this.value4 = Objects.requireNonNull(value4, "value4"); + } + + @Override + public Context put(Object key, Object value) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(value, "value"); + + if(this.key1.equals(key)){ + return new Context4(key, value, key2, value2, key3, value3, key4, value4); + } + + if (this.key2.equals(key)) { + return new Context4(key1, value1, key, value, key3, value3, key4, value4); + } + + if (this.key3.equals(key)) { + return new Context4(key1, value1, key2, value2, key, value, key4, value4); + } + + if (this.key4.equals(key)) { + return new Context4(key1, value1, key2, value2, key3, value3, key, value); + } + + return new Context5(this.key1, this.value1, this.key2, this.value2, this.key3, this.value3, + this.key4, this.value4, key, value); + } + + @Override + public Context delete(Object key) { + Objects.requireNonNull(key, "key"); + + if(this.key1.equals(key)){ + return new Context3(key2, value2, key3, value3, key4, value4); + } + + if (this.key2.equals(key)) { + return new Context3(key1, value1, key3, value3, key4, value4); + } + + if (this.key3.equals(key)) { + return new Context3(key1, value1, key2, value2, key4, value4); + } + + if (this.key4.equals(key)) { + return new Context3(key1, value1, key2, value2, key3, value3); + } + + return this; + } + + @Override + public boolean hasKey(Object key) { + return this.key1.equals(key) || this.key2.equals(key) || this.key3.equals(key) || this.key4.equals(key); + } + + @Override + @SuppressWarnings("unchecked") + public T get(Object key) { + if (this.key1.equals(key)) { + return (T)this.value1; + } + if (this.key2.equals(key)) { + return (T)this.value2; + } + if (this.key3.equals(key)) { + return (T)this.value3; + } + if (this.key4.equals(key)) { + return (T)this.value4; + } + throw new NoSuchElementException("Context does not contain key: "+key); + } + + @Override + public int size() { + return 4; + } + + @Override + public Stream> stream() { + return Stream.of( + new AbstractMap.SimpleImmutableEntry<>(key1, value1), + new AbstractMap.SimpleImmutableEntry<>(key2, value2), + new AbstractMap.SimpleImmutableEntry<>(key3, value3), + new AbstractMap.SimpleImmutableEntry<>(key4, value4)); + } + + @Override + public Context putAllInto(Context base) { + return base + .put(this.key1, this.value1) + .put(this.key2, this.value2) + .put(this.key3, this.value3) + .put(this.key4, this.value4); + } + + @Override + public void unsafePutAllInto(ContextN other) { + other.accept(key1, value1); + other.accept(key2, value2); + other.accept(key3, value3); + other.accept(key4, value4); + } + + @Override + public String toString() { + return "Context4{" + key1 + '='+ value1 + ", " + key2 + '=' + value2 + ", " + + key3 + '=' + value3 + ", " + key4 + '=' + value4 + '}'; + } +} Index: 3rdParty_sources/reactor/reactor/util/context/Context5.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/Context5.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/Context5.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2017-2021 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.AbstractMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.stream.Stream; + +final class Context5 implements CoreContext { + + final Object key1; + final Object value1; + final Object key2; + final Object value2; + final Object key3; + final Object value3; + final Object key4; + final Object value4; + final Object key5; + final Object value5; + + Context5(Object key1, Object value1, + Object key2, Object value2, + Object key3, Object value3, + Object key4, Object value4, + Object key5, Object value5) { + //TODO merge null check and duplicate check in the util method + Context4.checkKeys(key1, key2, key3, key4, key5); + this.key1 = Objects.requireNonNull(key1, "key1"); + this.value1 = Objects.requireNonNull(value1, "value1"); + this.key2 = Objects.requireNonNull(key2, "key2"); + this.value2 = Objects.requireNonNull(value2, "value2"); + this.key3 = Objects.requireNonNull(key3, "key3"); + this.value3 = Objects.requireNonNull(value3, "value3"); + this.key4 = Objects.requireNonNull(key4, "key4"); + this.value4 = Objects.requireNonNull(value4, "value4"); + this.key5 = Objects.requireNonNull(key5, "key5"); + this.value5 = Objects.requireNonNull(value5, "value5"); + } + + @Override + public Context put(Object key, Object value) { + Objects.requireNonNull(key, "key"); + Objects.requireNonNull(value, "value"); + + if(this.key1.equals(key)){ + return new Context5(key, value, key2, value2, key3, value3, key4, value4, key5, value5); + } + + if (this.key2.equals(key)) { + return new Context5(key1, value1, key, value, key3, value3, key4, value4, key5, value5); + } + + if (this.key3.equals(key)) { + return new Context5(key1, value1, key2, value2, key, value, key4, value4, key5, value5); + } + + if (this.key4.equals(key)) { + return new Context5(key1, value1, key2, value2, key3, value3, key, value, key5, value5); + } + + if (this.key5.equals(key)) { + return new Context5(key1, value1, key2, value2, key3, value3, key4, value4, key, value); + } + + return new ContextN(key1, value1, key2, value2, key3, value3, key4, value4, key5, value5, key, value); + } + + @Override + public Context delete(Object key) { + Objects.requireNonNull(key, "key"); + + if(this.key1.equals(key)){ + return new Context4(key2, value2, key3, value3, key4, value4, key5, value5); + } + + if (this.key2.equals(key)) { + return new Context4(key1, value1, key3, value3, key4, value4, key5, value5); + } + + if (this.key3.equals(key)) { + return new Context4(key1, value1, key2, value2, key4, value4, key5, value5); + } + + if (this.key4.equals(key)) { + return new Context4(key1, value1, key2, value2, key3, value3, key5, value5); + } + + if (this.key5.equals(key)) { + return new Context4(key1, value1, key2, value2, key3, value3, key4, value4); + } + + return this; + } + + @Override + public boolean hasKey(Object key) { + return this.key1.equals(key) || this.key2.equals(key) || this.key3.equals(key) + || this.key4.equals(key) || this.key5.equals(key); + } + + @Override + @SuppressWarnings("unchecked") + public T get(Object key) { + if (this.key1.equals(key)) { + return (T)this.value1; + } + if (this.key2.equals(key)) { + return (T)this.value2; + } + if (this.key3.equals(key)) { + return (T)this.value3; + } + if (this.key4.equals(key)) { + return (T)this.value4; + } + if (this.key5.equals(key)) { + return (T)this.value5; + } + throw new NoSuchElementException("Context does not contain key: "+key); + } + + @Override + public int size() { + return 5; + } + + @Override + public Stream> stream() { + return Stream.of( + new AbstractMap.SimpleImmutableEntry<>(key1, value1), + new AbstractMap.SimpleImmutableEntry<>(key2, value2), + new AbstractMap.SimpleImmutableEntry<>(key3, value3), + new AbstractMap.SimpleImmutableEntry<>(key4, value4), + new AbstractMap.SimpleImmutableEntry<>(key5, value5)); + } + + @Override + public Context putAllInto(Context base) { + return base + .put(this.key1, this.value1) + .put(this.key2, this.value2) + .put(this.key3, this.value3) + .put(this.key4, this.value4) + .put(this.key5, this.value5); + } + + @Override + public void unsafePutAllInto(ContextN other) { + other.accept(key1, value1); + other.accept(key2, value2); + other.accept(key3, value3); + other.accept(key4, value4); + other.accept(key5, value5); + } + + @Override + public String toString() { + return "Context5{" + key1 + '='+ value1 + ", " + key2 + '=' + value2 + ", " + + key3 + '=' + value3 + ", " + key4 + '=' + value4 + ", " + key5 + '=' + value5 + '}'; + } +} Index: 3rdParty_sources/reactor/reactor/util/context/ContextN.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/ContextN.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/ContextN.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2015-2021 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.AbstractMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import reactor.util.annotation.Nullable; + +@SuppressWarnings("unchecked") +final class ContextN extends LinkedHashMap + implements CoreContext, BiConsumer, Consumer> { + + ContextN(Object key1, Object value1, Object key2, Object value2, + Object key3, Object value3, Object key4, Object value4, + Object key5, Object value5, Object key6, Object value6) { + super(6, 1f); + //accept below stands in for "inner put" + accept(key1, value1); + accept(key2, value2); + accept(key3, value3); + accept(key4, value4); + accept(key5, value5); + accept(key6, value6); + } + + /** + * Creates a new {@link ContextN} with values from the provided {@link Map} + * + * @param originalToCopy a {@link Map} to populate entries from. MUST NOT contain null keys/values + */ + ContextN(Map originalToCopy) { + super(Objects.requireNonNull(originalToCopy, "originalToCopy")); + } + + ContextN(int initialCapacity) { + super(initialCapacity, 1.0f); + } + + //this performs an inner put to the actual map, and also allows passing `this` directly to + //Map#forEach + @Override + public void accept(Object key, Object value) { + super.put(Objects.requireNonNull(key, "key"), + Objects.requireNonNull(value, "value")); + } + + //this performs an inner put of the entry to the actual map + @Override + public void accept(Entry entry) { + accept(entry.getKey(), entry.getValue()); + } + + /** + * Note that this method overrides {@link LinkedHashMap#put(Object, Object)}. + * Consider using {@link #accept(Object, Object)} instead for putting items into the map. + * + * @param key the key to add/update in the new {@link Context} + * @param value the value to associate to the key in the new {@link Context} + */ + @Override + public Context put(Object key, Object value) { + ContextN newContext = new ContextN(this); + newContext.accept(key, value); + return newContext; + } + + @Override + public Context delete(Object key) { + Objects.requireNonNull(key, "key"); + if (!hasKey(key)) { + return this; + } + + int s = size() - 1; + if (s == 5) { + @SuppressWarnings("unchecked") + Entry[] arr = new Entry[s]; + int idx = 0; + for (Entry entry : entrySet()) { + if (!entry.getKey().equals(key)) { + arr[idx] = entry; + idx++; + } + } + return new Context5( + arr[0].getKey(), arr[0].getValue(), + arr[1].getKey(), arr[1].getValue(), + arr[2].getKey(), arr[2].getValue(), + arr[3].getKey(), arr[3].getValue(), + arr[4].getKey(), arr[4].getValue()); + } + + ContextN newInstance = new ContextN(this); + newInstance.remove(key); + return newInstance; + } + + @Override + public boolean hasKey(Object key) { + return super.containsKey(key); + } + + @Override + public Object get(Object key) { + Object o = super.get(key); + if (o != null) { + return o; + } + throw new NoSuchElementException("Context does not contain key: "+key); + } + + @Override + @Nullable + public Object getOrDefault(Object key, @Nullable Object defaultValue) { + Object o = super.get(key); + if (o != null) { + return o; + } + return defaultValue; + } + + @Override + public Stream> stream() { + return entrySet().stream().map(AbstractMap.SimpleImmutableEntry::new); + } + + @Override + public Context putAllInto(Context base) { + if (base instanceof ContextN) { + ContextN newContext = new ContextN(base.size() + this.size()); + newContext.putAll((Map) base); + newContext.putAll((Map) this); + return newContext; + } + + Context[] holder = new Context[]{base}; + forEach((k, v) -> holder[0] = holder[0].put(k, v)); + return holder[0]; + } + + @Override + public void unsafePutAllInto(ContextN other) { + other.putAll((Map) this); + } + + @Override + public Context putAll(ContextView other) { + if (other.isEmpty()) return this; + + // slightly less wasteful implementation for non-core context: + // only collect the other since we already have a map for this. + ContextN newContext = new ContextN(this); + if (other instanceof CoreContext) { + CoreContext coreContext = (CoreContext) other; + coreContext.unsafePutAllInto(newContext); + } + else { + // avoid Collector to reduce the allocations + other.stream().sequential().forEach(newContext); + } + + return newContext; + } + + @Override + public String toString() { + return "ContextN" + super.toString(); + } +} Index: 3rdParty_sources/reactor/reactor/util/context/ContextView.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/ContextView.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/ContextView.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2020-2021 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.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Stream; + +import reactor.util.annotation.Nullable; + +/** + * A read-only view of a collection of key/value pairs that is propagated between components + * such as operators via the context protocol. Contexts are ideal to transport orthogonal + * information such as tracing or security tokens. + *

+ * {@link Context} is an immutable variant of the same key/value pairs structure which exposes + * a write API that returns new instances on each write. + * + * @author Simon Baslé + * @since 3.4.0 + */ +public interface ContextView { + + /** + * Resolve a value given a key that exists within the {@link Context}, or throw + * a {@link NoSuchElementException} if the key is not present. + * + * @param key a lookup key to resolve the value within the context + * @param an unchecked casted generic for fluent typing convenience + * + * @return the value resolved for this key (throws if key not found) + * + * @throws NoSuchElementException when the given key is not present + * @see #getOrDefault(Object, Object) + * @see #getOrEmpty(Object) + * @see #hasKey(Object) + */ + T get(Object key); + + /** + * Resolve a value given a type key within the {@link Context}. + * + * @param key a type key to resolve the value within the context + * @param an unchecked casted generic for fluent typing convenience + * + * @return the value resolved for this type key (throws if key not found) + * + * @throws NoSuchElementException when the given type key is not present + * @see #getOrDefault(Object, Object) + * @see #getOrEmpty(Object) + */ + default T get(Class key) { + T v = get((Object) key); + if (key.isInstance(v)) { + return v; + } + throw new NoSuchElementException("Context does not contain a value of type " + key.getName()); + } + + /** + * Resolve a value given a key within the {@link Context}. If unresolved return the + * passed default value. + * + * @param key a lookup key to resolve the value within the context + * @param defaultValue a fallback value if key doesn't resolve + * + * @return the value resolved for this key, or the given default if not present + */ + @Nullable + default T getOrDefault(Object key, @Nullable T defaultValue) { + if (!hasKey(key)) { + return defaultValue; + } + return get(key); + } + + /** + * Resolve a value given a key within the {@link Context}. + * + * @param key a lookup key to resolve the value within the context + * + * @return an {@link Optional} of the value for that key. + */ + default Optional getOrEmpty(Object key) { + if (hasKey(key)) { + return Optional.of(get(key)); + } + return Optional.empty(); + } + + /** + * Return true if a particular key resolves to a value within the {@link Context}. + * + * @param key a lookup key to test for + * + * @return true if this context contains the given key + */ + boolean hasKey(Object key); + + /** + * Return true if the {@link Context} is empty. + * + * @return true if the {@link Context} is empty. + */ + default boolean isEmpty() { + return size() == 0; + } + + /** + * Return the size of this {@link Context}, the number of immutable key/value pairs stored inside it. + * + * @return the size of the {@link Context} + */ + int size(); + + /** + * Stream key/value pairs from this {@link Context} + * + * @return a {@link Stream} of key/value pairs held by this context + */ + Stream> stream(); +} Index: 3rdParty_sources/reactor/reactor/util/context/CoreContext.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/CoreContext.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/CoreContext.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019-2021 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; + +/** + * Abstract base to optimize interactions between reactor core {@link Context} implementations. + * + * @author Simon Baslé + */ +interface CoreContext extends Context { + + @Override + default boolean isEmpty() { + // Overridden in Context0#isEmpty + return false; + } + + @Override + default Context putAll(ContextView other) { + if (other.isEmpty()) return this; + + if (other instanceof CoreContext) { + CoreContext coreContext = (CoreContext) other; + return coreContext.putAllInto(this); + } + + ContextN newContext = new ContextN(this.size() + other.size()); + this.unsafePutAllInto(newContext); + other.stream().sequential().forEach(newContext); + if (newContext.size() <= 5) { + // make it return Context{1-5} + return Context.of((Map) newContext); + } + return newContext; + } + + /** + * Let this Context add its internal values to the given base Context, avoiding creating + * intermediate holders for key-value pairs as much as possible. + * + * @param base the {@link Context} in which we're putting all our values + * @return a new context containing all the base values merged with all our values + */ + Context putAllInto(Context base); + + /** + * Let this Context add its internal values to the given ContextN, mutating it, + * but avoiding creating intermediate holders for key-value pairs as much as possible. + * + * @param other the {@link ContextN} we're mutating by putting all our values into + */ + void unsafePutAllInto(ContextN other); + +} Index: 3rdParty_sources/reactor/reactor/util/context/package-info.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/context/package-info.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/context/package-info.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +/** + * Miscellaneous utility classes, such as loggers, tuples or queue suppliers and implementations. + */ +@NonNullApi +package reactor.util.context; + +import reactor.util.annotation.NonNullApi; \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/function/Tuple2.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/Tuple2.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/Tuple2.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2016-2021 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.function; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import reactor.util.annotation.NonNull; +import reactor.util.annotation.Nullable; + +/** + * A tuple that holds two non-null values. + * + * @param The type of the first non-null value held by this tuple + * @param The type of the second non-null value held by this tuple + * @author Jon Brisbin + * @author Stephane Maldini + */ +@SuppressWarnings("rawtypes") +public class Tuple2 implements Iterable, Serializable { + + private static final long serialVersionUID = -3518082018884860684L; + + @NonNull final T1 t1; + @NonNull final T2 t2; + + Tuple2(T1 t1, T2 t2) { + this.t1 = Objects.requireNonNull(t1, "t1"); + this.t2 = Objects.requireNonNull(t2, "t2"); + } + + /** + * Type-safe way to get the first object of this {@link Tuples}. + * + * @return The first object + */ + public T1 getT1() { + return t1; + } + + /** + * Type-safe way to get the second object of this {@link Tuples}. + * + * @return The second object + */ + public T2 getT2() { + return t2; + } + + /** + * Map the left-hand part (T1) of this {@link Tuple2} into a different value and type, + * keeping the right-hand part (T2). + * + * @param mapper the mapping {@link Function} for the left-hand part + * @param the new type for the left-hand part + * @return a new {@link Tuple2} with a different left (T1) value + */ + public Tuple2 mapT1(Function mapper) { + return new Tuple2<>(mapper.apply(t1), t2); + } + + /** + * Map the right-hand part (T2) of this {@link Tuple2} into a different value and type, + * keeping the left-hand part (T1). + * + * @param mapper the mapping {@link Function} for the right-hand part + * @param the new type for the right-hand part + * @return a new {@link Tuple2} with a different right (T2) value + */ + public Tuple2 mapT2(Function mapper) { + return new Tuple2<>(t1, mapper.apply(t2)); + } + + /** + * Get the object at the given index. + * + * @param index The index of the object to retrieve. Starts at 0. + * @return The object or {@literal null} if out of bounds. + */ + @Nullable + public Object get(int index) { + switch (index) { + case 0: + return t1; + case 1: + return t2; + default: + return null; + } + } + + /** + * Turn this {@code Tuple} into a {@link List List<Object>}. + * The list isn't tied to this Tuple but is a copy with limited + * mutability ({@code add} and {@code remove} are not supported, but {@code set} is). + * + * @return A copy of the tuple as a new {@link List List<Object>}. + */ + public List toList() { + return Arrays.asList(toArray()); + } + + /** + * Turn this {@code Tuple} into a plain {@code Object[]}. + * The array isn't tied to this Tuple but is a copy. + * + * @return A copy of the tuple as a new {@link Object Object[]}. + */ + public Object[] toArray() { + return new Object[]{t1, t2}; + } + + /** + * Return an immutable {@link Iterator Iterator<Object>} around + * the content of this {@code Tuple}. + * + * @implNote As an {@link Iterator} is always tied to its {@link Iterable} source by + * definition, the iterator cannot be mutable without the iterable also being mutable. + * Since {@link Tuples} are immutable, so is the {@link Iterator} + * returned by this method. + * + * @return An unmodifiable {@link Iterator} over the elements in this Tuple. + */ + @Override + public Iterator iterator() { + return Collections.unmodifiableList(toList()).iterator(); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Tuple2 tuple2 = (Tuple2) o; + + return t1.equals(tuple2.t1) && t2.equals(tuple2.t2); + + } + + @Override + public int hashCode() { + int result = size(); + result = 31 * result + t1.hashCode(); + result = 31 * result + t2.hashCode(); + return result; + } + + /** + * Return the number of elements in this {@literal Tuples}. + * + * @return The size of this {@literal Tuples}. + */ + public int size() { + return 2; + } + + /** + * A Tuple String representation is the comma separated list of values, enclosed + * in square brackets. + * @return the Tuple String representation + */ + @Override + public final String toString() { + return Tuples.tupleStringRepresentation(toArray()).insert(0, '[').append(']').toString(); + } +} Index: 3rdParty_sources/reactor/reactor/util/function/Tuple3.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/Tuple3.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/Tuple3.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2016-2021 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.function; + +import java.util.Objects; +import java.util.function.Function; + +import reactor.util.annotation.NonNull; +import reactor.util.annotation.Nullable; + +/** + * A tuple that holds three non-null values. + * + * @param The type of the first non-null value held by this tuple + * @param The type of the second non-null value held by this tuple + * @param The type of the third non-null value held by this tuple + * @author Jon Brisbin + * @author Stephane Maldini + */ +public class Tuple3 extends Tuple2 { + + private static final long serialVersionUID = -4430274211524723033L; + + @NonNull final T3 t3; + + Tuple3(T1 t1, T2 t2, T3 t3) { + super(t1, t2); + this.t3 = Objects.requireNonNull(t3, "t3"); + } + + /** + * Type-safe way to get the third object of this {@link Tuples}. + * + * @return The third object + */ + public T3 getT3() { + return t3; + } + + /** + * Map the 1st part (T1) of this {@link Tuple3} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T1 part + * @param the new type for the T1 part + * @return a new {@link Tuple3} with a different T1 value + */ + public Tuple3 mapT1(Function mapper) { + return new Tuple3<>(mapper.apply(t1), t2, t3); + } + + /** + * Map the 2nd part (T2) of this {@link Tuple3} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T2 part + * @param the new type for the T2 part + * @return a new {@link Tuple3} with a different T2 value + */ + public Tuple3 mapT2(Function mapper) { + return new Tuple3<>(t1, mapper.apply(t2), t3); + } + + /** + * Map the 3rd part (T3) of this {@link Tuple3} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T3 part + * @param the new type for the T3 part + * @return a new {@link Tuple3} with a different T3 value + */ + public Tuple3 mapT3(Function mapper) { + return new Tuple3<>(t1, t2, mapper.apply(t3)); + } + + @Nullable + @Override + public Object get(int index) { + switch (index) { + case 0: + return t1; + case 1: + return t2; + case 2: + return t3; + default: + return null; + } + } + + @Override + public Object[] toArray() { + return new Object[]{t1, t2, t3}; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof Tuple3)) return false; + if (!super.equals(o)) return false; + + @SuppressWarnings("rawtypes") + Tuple3 tuple3 = (Tuple3) o; + + return t3.equals(tuple3.t3); + } + + @Override + public int size() { + return 3; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + t3.hashCode(); + return result; + } +} Index: 3rdParty_sources/reactor/reactor/util/function/Tuple4.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/Tuple4.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/Tuple4.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2016-2021 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.function; + +import java.util.Objects; +import java.util.function.Function; + +import reactor.util.annotation.NonNull; +import reactor.util.annotation.Nullable; + +/** + * A tuple that holds four non-null values + * + * @param The type of the first non-null value held by this tuple + * @param The type of the second non-null value held by this tuple + * @param The type of the third non-null value held by this tuple + * @param The type of the fourth non-null value held by this tuple + * @author Jon Brisbin + * @author Stephane Maldini + */ +public class Tuple4 extends Tuple3 { + + private static final long serialVersionUID = -4898704078143033129L; + + @NonNull final T4 t4; + + Tuple4(T1 t1, T2 t2, T3 t3, T4 t4) { + super( t1, t2, t3); + this.t4 = Objects.requireNonNull(t4, "t4"); + } + + /** + * Type-safe way to get the fourth object of this {@link Tuples}. + * + * @return The fourth object + */ + public T4 getT4() { + return t4; + } + + /** + * Map the 1st part (T1) of this {@link Tuple4} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T1 part + * @param the new type for the T1 part + * @return a new {@link Tuple4} with a different T1 value + */ + public Tuple4 mapT1(Function mapper) { + return new Tuple4<>(mapper.apply(t1), t2, t3, t4); + } + + /** + * Map the 2nd part (T2) of this {@link Tuple4} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T2 part + * @param the new type for the T2 part + * @return a new {@link Tuple4} with a different T2 value + */ + public Tuple4 mapT2(Function mapper) { + return new Tuple4<>(t1, mapper.apply(t2), t3, t4); + } + + /** + * Map the 3rd part (T3) of this {@link Tuple4} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T3 part + * @param the new type for the T3 part + * @return a new {@link Tuple4} with a different T3 value + */ + public Tuple4 mapT3(Function mapper) { + return new Tuple4<>(t1, t2, mapper.apply(t3), t4); + } + + /** + * Map the 4th part (T4) of this {@link Tuple4} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T4 part + * @param the new type for the T4 part + * @return a new {@link Tuple4} with a different T4 value + */ + public Tuple4 mapT4(Function mapper) { + return new Tuple4<>(t1, t2, t3, mapper.apply(t4)); + } + + @Nullable + @Override + public Object get(int index) { + switch (index) { + case 0: + return t1; + case 1: + return t2; + case 2: + return t3; + case 3: + return t4; + default: + return null; + } + } + + @Override + public Object[] toArray() { + return new Object[]{t1, t2, t3, t4}; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof Tuple4)) return false; + if (!super.equals(o)) return false; + + @SuppressWarnings("rawtypes") + Tuple4 tuple4 = (Tuple4) o; + + return t4.equals(tuple4.t4); + + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + t4.hashCode(); + return result; + } + + @Override + public int size() { + return 4; + } +} Index: 3rdParty_sources/reactor/reactor/util/function/Tuple5.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/Tuple5.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/Tuple5.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2016-2021 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.function; + +import java.util.Objects; +import java.util.function.Function; + +import reactor.util.annotation.NonNull; +import reactor.util.annotation.Nullable; + +/** + * A tuple that holds five non-null values + * + * @param The type of the first non-null value held by this tuple + * @param The type of the second non-null value held by this tuple + * @param The type of the third non-null value held by this tuple + * @param The type of the fourth non-null value held by this tuple + * @param The type of the fifth non-null value held by this tuple + * @author Jon Brisbin + * @author Stephane Maldini + */ +public class Tuple5 extends Tuple4 { + + private static final long serialVersionUID = 3541548454198133275L; + + @NonNull final T5 t5; + + Tuple5(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) { + super(t1, t2, t3, t4); + this.t5 = Objects.requireNonNull(t5, "t5"); + } + + /** + * Type-safe way to get the fifth object of this {@link Tuples}. + * + * @return The fifth object + */ + public T5 getT5() { + return t5; + } + + /** + * Map the 1st part (T1) of this {@link Tuple5} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T1 part + * @param the new type for the T1 part + * @return a new {@link Tuple5} with a different T1 value + */ + public Tuple5 mapT1(Function mapper) { + return new Tuple5<>(mapper.apply(t1), t2, t3, t4, t5); + } + + /** + * Map the 2nd part (T2) of this {@link Tuple5} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T2 part + * @param the new type for the T2 part + * @return a new {@link Tuple5} with a different T2 value + */ + public Tuple5 mapT2(Function mapper) { + return new Tuple5<>(t1, mapper.apply(t2), t3, t4, t5); + } + + /** + * Map the 3rd part (T3) of this {@link Tuple5} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T3 part + * @param the new type for the T3 part + * @return a new {@link Tuple5} with a different T3 value + */ + public Tuple5 mapT3(Function mapper) { + return new Tuple5<>(t1, t2, mapper.apply(t3), t4, t5); + } + + /** + * Map the 4th part (T4) of this {@link Tuple5} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T4 part + * @param the new type for the T4 part + * @return a new {@link Tuple5} with a different T4 value + */ + public Tuple5 mapT4(Function mapper) { + return new Tuple5<>(t1, t2, t3, mapper.apply(t4), t5); + } + + /** + * Map the 5th part (T5) of this {@link Tuple5} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T5 part + * @param the new type for the T5 part + * @return a new {@link Tuple5} with a different T5 value + */ + public Tuple5 mapT5(Function mapper) { + return new Tuple5<>(t1, t2, t3, t4, mapper.apply(t5)); + } + + @Nullable + @Override + public Object get(int index) { + switch (index) { + case 0: + return t1; + case 1: + return t2; + case 2: + return t3; + case 3: + return t4; + case 4: + return t5; + default: + return null; + } + } + + @Override + public Object[] toArray() { + return new Object[]{t1, t2, t3, t4, t5}; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof Tuple5)) return false; + if (!super.equals(o)) return false; + + @SuppressWarnings("rawtypes") + Tuple5 tuple5 = (Tuple5) o; + + return t5.equals(tuple5.t5); + + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + t5.hashCode(); + return result; + } + + @Override + public int size() { + return 5; + } +} Index: 3rdParty_sources/reactor/reactor/util/function/Tuple6.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/Tuple6.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/Tuple6.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2016-2021 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.function; + +import java.util.Objects; +import java.util.function.Function; + +import reactor.util.annotation.NonNull; +import reactor.util.annotation.Nullable; + +/** + * A tuple that holds six values + * + * @param The type of the first value held by this tuple + * @param The type of the second value held by this tuple + * @param The type of the third value held by this tuple + * @param The type of the fourth value held by this tuple + * @param The type of the fifth value held by this tuple + * @param The type of the sixth value held by this tuple + * @author Jon Brisbin + * @author Stephane Maldini + */ +public class Tuple6 extends Tuple5 { + + private static final long serialVersionUID = 770306356087176830L; + + @NonNull final T6 t6; + + Tuple6(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) { + super(t1, t2, t3, t4, t5); + this.t6 = Objects.requireNonNull(t6, "t6"); + } + + /** + * Type-safe way to get the sixth object of this {@link Tuples}. + * + * @return The sixth object + */ + public T6 getT6() { + return t6; + } + + /** + * Map the 1st part (T1) of this {@link Tuple6} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T1 part + * @param the new type for the T1 part + * @return a new {@link Tuple6} with a different T1 value + */ + public Tuple6 mapT1(Function mapper) { + return new Tuple6<>(mapper.apply(t1), t2, t3, t4, t5, t6); + } + + /** + * Map the 2nd part (T2) of this {@link Tuple6} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T2 part + * @param the new type for the T2 part + * @return a new {@link Tuple6} with a different T2 value + */ + public Tuple6 mapT2(Function mapper) { + return new Tuple6<>(t1, mapper.apply(t2), t3, t4, t5, t6); + } + + /** + * Map the 3rd part (T3) of this {@link Tuple6} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T3 part + * @param the new type for the T3 part + * @return a new {@link Tuple6} with a different T3 value + */ + public Tuple6 mapT3(Function mapper) { + return new Tuple6<>(t1, t2, mapper.apply(t3), t4, t5, t6); + } + + /** + * Map the 4th part (T4) of this {@link Tuple6} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T4 part + * @param the new type for the T4 part + * @return a new {@link Tuple6} with a different T4 value + */ + public Tuple6 mapT4(Function mapper) { + return new Tuple6<>(t1, t2, t3, mapper.apply(t4), t5, t6); + } + + /** + * Map the 5th part (T5) of this {@link Tuple6} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T5 part + * @param the new type for the T5 part + * @return a new {@link Tuple6} with a different T5 value + */ + public Tuple6 mapT5(Function mapper) { + return new Tuple6<>(t1, t2, t3, t4, mapper.apply(t5), t6); + } + + /** + * Map the 6th part (T6) of this {@link Tuple6} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T6 part + * @param the new type for the T6 part + * @return a new {@link Tuple6} with a different T6 value + */ + public Tuple6 mapT6(Function mapper) { + return new Tuple6<>(t1, t2, t3, t4, t5, mapper.apply(t6)); + } + + @Nullable + @Override + public Object get(int index) { + switch (index) { + case 0: + return t1; + case 1: + return t2; + case 2: + return t3; + case 3: + return t4; + case 4: + return t5; + case 5: + return t6; + default: + return null; + } + } + + @Override + public Object[] toArray() { + return new Object[]{t1, t2, t3, t4, t5, t6}; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof Tuple6)) return false; + if (!super.equals(o)) return false; + + @SuppressWarnings("rawtypes") + Tuple6 tuple6 = (Tuple6) o; + + return t6.equals(tuple6.t6); + + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + t6.hashCode(); + return result; + } + + @Override + public int size() { + return 6; + } +} Index: 3rdParty_sources/reactor/reactor/util/function/Tuple7.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/Tuple7.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/Tuple7.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2016-2021 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.function; + +import java.util.Objects; +import java.util.function.Function; + +import reactor.util.annotation.NonNull; +import reactor.util.annotation.Nullable; + +/** + * A tuple that holds seven non-null values + * + * @param The type of the first non-null value held by this tuple + * @param The type of the second non-null value held by this tuple + * @param The type of the third non-null value held by this tuple + * @param The type of the fourth non-null value held by this tuple + * @param The type of the fifth non-null value held by this tuple + * @param The type of the sixth non-null value held by this tuple + * @param The type of the seventh non-null value held by this tuple + * @author Jon Brisbin + * @author Stephane Maldini + */ +public class Tuple7 extends Tuple6 { + + private static final long serialVersionUID = -8002391247456579281L; + + @NonNull final T7 t7; + + Tuple7(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) { + super( t1, t2, t3, t4, t5, t6); + this.t7 = Objects.requireNonNull(t7, "t7"); + } + + /** + * Type-safe way to get the seventh object of this {@link Tuples}. + * + * @return The seventh object + */ + public T7 getT7() { + return t7; + } + + /** + * Map the 1st part (T1) of this {@link Tuple7} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T1 part + * @param the new type for the T1 part + * @return a new {@link Tuple7} with a different T1 value + */ + public Tuple7 mapT1(Function mapper) { + return new Tuple7<>(mapper.apply(t1), t2, t3, t4, t5, t6, t7); + } + + /** + * Map the 2nd part (T2) of this {@link Tuple7} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T2 part + * @param the new type for the T2 part + * @return a new {@link Tuple7} with a different T2 value + */ + public Tuple7 mapT2(Function mapper) { + return new Tuple7<>(t1, mapper.apply(t2), t3, t4, t5, t6, t7); + } + + /** + * Map the 3rd part (T3) of this {@link Tuple7} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T3 part + * @param the new type for the T3 part + * @return a new {@link Tuple7} with a different T3 value + */ + public Tuple7 mapT3(Function mapper) { + return new Tuple7<>(t1, t2, mapper.apply(t3), t4, t5, t6, t7); + } + + /** + * Map the 4th part (T4) of this {@link Tuple7} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T4 part + * @param the new type for the T4 part + * @return a new {@link Tuple7} with a different T4 value + */ + public Tuple7 mapT4(Function mapper) { + return new Tuple7<>(t1, t2, t3, mapper.apply(t4), t5, t6, t7); + } + + /** + * Map the 5th part (T5) of this {@link Tuple7} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T5 part + * @param the new type for the T5 part + * @return a new {@link Tuple7} with a different T5 value + */ + public Tuple7 mapT5(Function mapper) { + return new Tuple7<>(t1, t2, t3, t4, mapper.apply(t5), t6, t7); + } + + /** + * Map the 6th part (T6) of this {@link Tuple7} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T6 part + * @param the new type for the T6 part + * @return a new {@link Tuple7} with a different T6 value + */ + public Tuple7 mapT6(Function mapper) { + return new Tuple7<>(t1, t2, t3, t4, t5, mapper.apply(t6), t7); + } + + /** + * Map the 7th part (T7) of this {@link Tuple7} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T7 part + * @param the new type for the T7 part + * @return a new {@link Tuple7} with a different T7 value + */ + public Tuple7 mapT7(Function mapper) { + return new Tuple7<>(t1, t2, t3, t4, t5, t6, mapper.apply(t7)); + } + + @Nullable + @Override + public Object get(int index) { + switch (index) { + case 0: + return t1; + case 1: + return t2; + case 2: + return t3; + case 3: + return t4; + case 4: + return t5; + case 5: + return t6; + case 6: + return t7; + default: + return null; + } + } + + @Override + public Object[] toArray() { + return new Object[]{t1, t2, t3, t4, t5, t6, t7}; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof Tuple7)) return false; + if (!super.equals(o)) return false; + + @SuppressWarnings("rawtypes") + Tuple7 tuple7 = (Tuple7) o; + + return t7.equals(tuple7.t7); + + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + t7.hashCode(); + return result; + } + + @Override + public int size() { + return 7; + } +} Index: 3rdParty_sources/reactor/reactor/util/function/Tuple8.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/Tuple8.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/Tuple8.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2016-2021 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.function; + +import java.util.Objects; +import java.util.function.Function; + +import reactor.util.annotation.NonNull; +import reactor.util.annotation.Nullable; + +/** + * A tuple that holds eight values + * + * @param The type of the first value held by this tuple + * @param The type of the second value held by this tuple + * @param The type of the third value held by this tuple + * @param The type of the fourth value held by this tuple + * @param The type of the fifth value held by this tuple + * @param The type of the sixth value held by this tuple + * @param The type of the seventh value held by this tuple + * @param The type of the eighth value held by this tuple + * @author Jon Brisbin + * @author Stephane Maldini + */ +public class Tuple8 extends + Tuple7 { + + private static final long serialVersionUID = -8746796646535446242L; + + @NonNull final T8 t8; + + Tuple8(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) { + super(t1, t2, t3, t4, t5, t6, t7); + this.t8 = Objects.requireNonNull(t8, "t8"); + } + + /** + * Type-safe way to get the eighth object of this {@link Tuples}. + * + * @return The eighth object + */ + public T8 getT8() { + return t8; + } + + /** + * Map the 1st part (T1) of this {@link Tuple8} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T1 part + * @param the new type for the T1 part + * @return a new {@link Tuple8} with a different T1 value + */ + public Tuple8 mapT1(Function mapper) { + return new Tuple8<>(mapper.apply(t1), t2, t3, t4, t5, t6, t7, t8); + } + + /** + * Map the 2nd part (T2) of this {@link Tuple8} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T2 part + * @param the new type for the T2 part + * @return a new {@link Tuple8} with a different T2 value + */ + public Tuple8 mapT2(Function mapper) { + return new Tuple8<>(t1, mapper.apply(t2), t3, t4, t5, t6, t7, t8); + } + + /** + * Map the 3rd part (T3) of this {@link Tuple8} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T3 part + * @param the new type for the T3 part + * @return a new {@link Tuple8} with a different T3 value + */ + public Tuple8 mapT3(Function mapper) { + return new Tuple8<>(t1, t2, mapper.apply(t3), t4, t5, t6, t7, t8); + } + + /** + * Map the 4th part (T4) of this {@link Tuple8} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T4 part + * @param the new type for the T4 part + * @return a new {@link Tuple8} with a different T4 value + */ + public Tuple8 mapT4(Function mapper) { + return new Tuple8<>(t1, t2, t3, mapper.apply(t4), t5, t6, t7, t8); + } + + /** + * Map the 5th part (T5) of this {@link Tuple8} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T5 part + * @param the new type for the T5 part + * @return a new {@link Tuple8} with a different T5 value + */ + public Tuple8 mapT5(Function mapper) { + return new Tuple8<>(t1, t2, t3, t4, mapper.apply(t5), t6, t7, t8); + } + + /** + * Map the 6th part (T6) of this {@link Tuple8} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T6 part + * @param the new type for the T6 part + * @return a new {@link Tuple8} with a different T6 value + */ + public Tuple8 mapT6(Function mapper) { + return new Tuple8<>(t1, t2, t3, t4, t5, mapper.apply(t6), t7, t8); + } + + /** + * Map the 7th part (T7) of this {@link Tuple8} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T7 part + * @param the new type for the T7 part + * @return a new {@link Tuple8} with a different T7 value + */ + public Tuple8 mapT7(Function mapper) { + return new Tuple8<>(t1, t2, t3, t4, t5, t6, mapper.apply(t7), t8); + } + + /** + * Map the 8th part (t8) of this {@link Tuple8} into a different value and type, + * keeping the other parts. + * + * @param mapper the mapping {@link Function} for the T8 part + * @param the new type for the T8 part + * @return a new {@link Tuple8} with a different T8 value + */ + public Tuple8 mapT8(Function mapper) { + return new Tuple8<>(t1, t2, t3, t4, t5, t6, t7, mapper.apply(t8)); + } + + @Nullable + @Override + public Object get(int index) { + switch (index) { + case 0: + return t1; + case 1: + return t2; + case 2: + return t3; + case 3: + return t4; + case 4: + return t5; + case 5: + return t6; + case 6: + return t7; + case 7: + return t8; + default: + return null; + } + } + + @Override + public Object[] toArray() { + return new Object[]{t1, t2, t3, t4, t5, t6, t7, t8}; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof Tuple8)) return false; + if (!super.equals(o)) return false; + + @SuppressWarnings("rawtypes") + Tuple8 tuple8 = (Tuple8) o; + + return t8.equals(tuple8.t8); + + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + t8.hashCode(); + return result; + } + + @Override + public int size() { + return 8; + } +} Index: 3rdParty_sources/reactor/reactor/util/function/TupleExtensions.kt =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/TupleExtensions.kt (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/TupleExtensions.kt (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2011-2021 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.function + + +/** + * Extension for [Tuple2] to work with destructuring declarations. + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions and import reactor.kotlin.core.util.function.component1", + ReplaceWith("component1()", "reactor.kotlin.core.util.function.component1")) +operator fun Tuple2.component1(): T = t1 + +/** + * Extension for [Tuple2] to work with destructuring declarations. + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions and import reactor.kotlin.core.util.function.component2", + ReplaceWith("component2()", "reactor.kotlin.core.util.function.component2")) +operator fun Tuple2<*, T>.component2(): T = t2 + +/** + * Extension for [Tuple3] to work with destructuring declarations. + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions and import reactor.kotlin.core.util.function.component3", + ReplaceWith("component3()", "reactor.kotlin.core.util.function.component3")) +operator fun Tuple3<*, *, T>.component3(): T = t3 + +/** + * Extension for [Tuple4] to work with destructuring declarations. + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions and import reactor.kotlin.core.util.function.component4", + ReplaceWith("component4()", "reactor.kotlin.core.util.function.component4")) +operator fun Tuple4<*, *, *, T>.component4(): T = t4 + +/** + * Extension for [Tuple5] to work with destructuring declarations. + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions and import reactor.kotlin.core.util.function.component5", + ReplaceWith("component5()", "reactor.kotlin.core.util.function.component5")) +operator fun Tuple5<*, *, *, *, T>.component5(): T = t5 + +/** + * Extension for [Tuple6] to work with destructuring declarations. + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions and import reactor.kotlin.core.util.function.component6", + ReplaceWith("component6()", "reactor.kotlin.core.util.function.component6")) +operator fun Tuple6<*, *, *, *, *, T>.component6(): T = t6 + +/** + * Extension for [Tuple7] to work with destructuring declarations. + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions and import reactor.kotlin.core.util.function.component7", + ReplaceWith("component7()", "reactor.kotlin.core.util.function.component7")) +operator fun Tuple7<*, *, *, *, *, *, T>.component7(): T = t7 + +/** + * Extension for [Tuple8] to work with destructuring declarations. + * + * @author DoHyung Kim + * @since 3.1 + */ +@Deprecated("To be removed in 3.3.0.RELEASE, replaced by module reactor-kotlin-extensions and import reactor.kotlin.core.util.function.component8", + ReplaceWith("component8()", "reactor.kotlin.core.util.function.component8")) +operator fun Tuple8<*, *, *, *, *, *, *, T>.component8(): T = t8 Index: 3rdParty_sources/reactor/reactor/util/function/Tuples.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/Tuples.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/Tuples.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2016-2021 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.function; + +import java.util.Collection; +import java.util.function.Function; + +/** + * A {@literal Tuples} is an immutable {@link Collection} of objects, each of which can be of an arbitrary type. + * + * @author Jon Brisbin + * @author Stephane Maldini + */ +@SuppressWarnings({"rawtypes"}) +public abstract class Tuples implements Function { + + /** + * Create a {@link Tuple2} with the given array if it is small + * enough to fit inside a {@link Tuple2} to {@link Tuple8}. + * + * @param list the content of the Tuple (size 1 to 8) + * @return The new {@link Tuple2}. + * @throws IllegalArgumentException if the array is not of length 1-8 + */ + public static Tuple2 fromArray(Object[] list) { + //noinspection ConstantConditions + if (list == null || list.length < 2) { + throw new IllegalArgumentException("null or too small array, need between 2 and 8 values"); + } + + switch (list.length){ + case 2: + return of(list[0], list[1]); + case 3: + return of(list[0], list[1], list[2]); + case 4: + return of(list[0], list[1], list[2], list[3]); + case 5: + return of(list[0], list[1], list[2], list[3], list[4]); + case 6: + return of(list[0], list[1], list[2], list[3], list[4], list[5]); + case 7: + return of(list[0], list[1], list[2], list[3], list[4], list[5], list[6]); + case 8: + return of(list[0], list[1], list[2], list[3], list[4], list[5], list[6], list[7]); + } + throw new IllegalArgumentException("too many arguments ("+ list.length + "), need between 2 and 8 values"); + } + + /** + * Create a {@link Tuple2} with the given objects. + * + * @param t1 The first value in the tuple. Not null. + * @param t2 The second value in the tuple. Not null. + * @param The type of the first value. + * @param The type of the second value. + * @return The new {@link Tuple2}. + */ + public static Tuple2 of(T1 t1, T2 t2) { + return new Tuple2<>(t1, t2); + } + + /** + * Create a {@link Tuple3} with the given objects. + * + * @param t1 The first value in the tuple. Not null. + * @param t2 The second value in the tuple. Not null. + * @param t3 The third value in the tuple. Not null. + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @return The new {@link Tuple3}. + */ + public static Tuple3 of(T1 t1, T2 t2, T3 t3) { + return new Tuple3<>(t1, t2, t3); + } + + /** + * Create a {@link Tuple4} with the given objects. + * + * @param t1 The first value in the tuple. Not null. + * @param t2 The second value in the tuple. Not null. + * @param t3 The third value in the tuple. Not null. + * @param t4 The fourth value in the tuple. Not null. + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @return The new {@link Tuple4}. + */ + public static Tuple4 of(T1 t1, T2 t2, T3 t3, T4 t4) { + return new Tuple4<>(t1, t2, t3, t4); + } + + /** + * Create a {@link Tuple5} with the given objects. + * + * @param t1 The first value in the tuple. Not null. + * @param t2 The second value in the tuple. Not null. + * @param t3 The third value in the tuple. Not null. + * @param t4 The fourth value in the tuple. Not null. + * @param t5 The fifth value in the tuple. Not null. + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @return The new {@link Tuple5}. + */ + public static Tuple5 of( + T1 t1, + T2 t2, + T3 t3, + T4 t4, + T5 t5) { + return new Tuple5<>(t1, t2, t3, t4, t5); + } + + /** + * Create a {@link Tuple6} with the given objects. + * + * @param t1 The first value in the tuple. Not null. + * @param t2 The second value in the tuple. Not null. + * @param t3 The third value in the tuple. Not null. + * @param t4 The fourth value in the tuple. Not null. + * @param t5 The fifth value in the tuple. Not null. + * @param t6 The sixth value in the tuple. Not null. + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the sixth value. + * @return The new {@link Tuple6}. + */ + public static Tuple6 of( + T1 t1, + T2 t2, + T3 t3, + T4 t4, + T5 t5, + T6 t6) { + return new Tuple6<>(t1, t2, t3, t4, t5, t6); + } + + /** + * Create a {@link Tuple7} with the given objects. + * + * @param t1 The first value in the tuple. Not null. + * @param t2 The second value in the tuple. Not null. + * @param t3 The third value in the tuple. Not null. + * @param t4 The fourth value in the tuple. Not null. + * @param t5 The fifth value in the tuple. Not null. + * @param t6 The sixth value in the tuple. Not null. + * @param t7 The seventh value in the tuple. Not null. + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the sixth value. + * @param The type of the seventh value. + * @return The new {@link Tuple7}. + */ + public static Tuple7 of( + T1 t1, + T2 t2, + T3 t3, + T4 t4, + T5 t5, + T6 t6, + T7 t7) { + return new Tuple7<>(t1, t2, t3, t4, t5, t6, t7); + } + + /** + * Create a {@link Tuple8} with the given objects. + * + * @param t1 The first value in the tuple. Not Null. + * @param t2 The second value in the tuple.Not Null. + * @param t3 The third value in the tuple. Not Null. + * @param t4 The fourth value in the tuple. Not Null. + * @param t5 The fifth value in the tuple. Not Null. + * @param t6 The sixth value in the tuple. Not Null. + * @param t7 The seventh value in the tuple. Not Null. + * @param t8 The eighth value in the tuple. Not Null. + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the sixth value. + * @param The type of the seventh value. + * @param The type of the eighth value. + * @return The new {@link Tuple8}. + */ + public static Tuple8 of( + T1 t1, + T2 t2, + T3 t3, + T4 t4, + T5 t5, + T6 t6, + T7 t7, + T8 t8) { + return new Tuple8<>(t1, t2, t3, t4, t5, t6, t7, t8); + } + + /** + * A converting function from Object array to {@link Tuples} + * + * @return The unchecked conversion function to {@link Tuples}. + */ + @SuppressWarnings("unchecked") + public static Function fnAny() { + return empty; + } + + /** + * A converting function from Object array to {@link Tuples} to R. + * + * @param The type of the return value. + * @param delegate the function to delegate to + * + * @return The unchecked conversion function to R. + */ + public static Function fnAny(final Function delegate) { + return objects -> delegate.apply(Tuples.fnAny().apply(objects)); + } + + /** + * A converting function from Object array to {@link Tuple2} + * + * @param The type of the first value. + * @param The type of the second value. + * + * @return The unchecked conversion function to {@link Tuple2}. + */ + @SuppressWarnings("unchecked") + public static Function> fn2() { + return empty; + } + + + /** + * A converting function from Object array to {@link Tuple3} + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * + * @return The unchecked conversion function to {@link Tuple3}. + */ + @SuppressWarnings("unchecked") + public static Function> fn3() { + return empty; + } + + /** + * A converting function from Object array to {@link Tuple3} to R. + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the return value. + * @param delegate the function to delegate to + * + * @return The unchecked conversion function to R. + */ + public static Function fn3(final Function, R> delegate) { + return objects -> delegate.apply(Tuples.fn3().apply(objects)); + } + + /** + * A converting function from Object array to {@link Tuple4} + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * + * @return The unchecked conversion function to {@link Tuple4}. + */ + @SuppressWarnings("unchecked") + public static Function> fn4() { + return empty; + } + + /** + * A converting function from Object array to {@link Tuple4} to R. + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the return value. + * @param delegate the function to delegate to + * + * @return The unchecked conversion function to R. + */ + public static Function fn4(final Function, R> delegate) { + return objects -> delegate.apply(Tuples.fn4().apply(objects)); + } + + /** + * A converting function from Object array to {@link Tuple5} + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * + * @return The unchecked conversion function to {@link Tuple5}. + */ + @SuppressWarnings("unchecked") + public static Function> fn5() { + return empty; + } + + /** + * A converting function from Object array to {@link Tuple4} to R. + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the return value. + * @param delegate the function to delegate to + * + * @return The unchecked conversion function to R. + */ + public static Function fn5(final Function, R> delegate) { + return objects -> delegate.apply(Tuples.fn5().apply(objects)); + } + + /** + * A converting function from Object array to {@link Tuple6} + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the sixth value. + * + * @return The unchecked conversion function to {@link Tuple6}. + */ + @SuppressWarnings("unchecked") + public static Function> fn6() { + return empty; + } + + /** + * A converting function from Object array to {@link Tuple6} to R. + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the sixth value. + * @param The type of the return value. + * @param delegate the function to delegate to + * + * @return The unchecked conversion function to R. + */ + public static Function fn6(final Function, R> delegate) { + return objects -> delegate.apply(Tuples.fn6().apply(objects)); + } + + /** + * A converting function from Object array to {@link Tuple7} + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the sixth value. + * @param The type of the seventh value. + * + * @return The unchecked conversion function to {@link Tuple7}. + */ + @SuppressWarnings("unchecked") + public static Function> fn7() { + return empty; + } + + /** + * A converting function from Object array to {@link Tuple7} to R. + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the sixth value. + * @param The type of the seventh value. + * @param The type of the return value. + * @param delegate the function to delegate to + * + * @return The unchecked conversion function to R. + */ + public static Function fn7(final Function, R> delegate) { + return objects -> delegate.apply(Tuples.fn7().apply(objects)); + } + + /** + * A converting function from Object array to {@link Tuple8} + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the sixth value. + * @param The type of the seventh value. + * @param The type of the eighth value. + * + * @return The unchecked conversion function to {@link Tuple8}. + */ + @SuppressWarnings("unchecked") + public static Function> fn8() { + return empty; + } + + /** + * A converting function from Object array to {@link Tuple8} + * + * @param The type of the first value. + * @param The type of the second value. + * @param The type of the third value. + * @param The type of the fourth value. + * @param The type of the fifth value. + * @param The type of the sixth value. + * @param The type of the seventh value. + * @param The type of the eighth value. + * @param The type of the return value. + * @param delegate the function to delegate to + * + * @return The unchecked conversion function to {@link Tuple8}. + */ + public static Function fn8(final Function, R> delegate) { + return objects -> delegate.apply(Tuples.fn8().apply(objects)); + } + + @Override + public Tuple2 apply(Object o) { + return fromArray((Object[])o); + } + + /** + * Prepare a string representation of the values suitable for a Tuple of any + * size by accepting an array of elements. This builds a {@link StringBuilder} + * containing the String representation of each object, comma separated. It manages + * nulls as well by putting an empty string and the comma. + * + * @param values the values of the tuple to represent + * @return a {@link StringBuilder} initialized with the string representation of the + * values in the Tuple. + */ + static StringBuilder tupleStringRepresentation(Object... values) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < values.length; i++) { + Object t = values[i]; + if (i != 0) { + sb.append(','); + } + if (t != null) { + sb.append(t); + } + } + return sb; + } + + + static final Tuples empty = new Tuples(){}; + + Tuples(){} +} Index: 3rdParty_sources/reactor/reactor/util/function/package-info.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/function/package-info.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/function/package-info.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +/** + * {@link reactor.util.function.Tuples Tuples} provide a type-safe way to specify multiple parameters. + */ +@NonNullApi +package reactor.util.function; + +import reactor.util.annotation.NonNullApi; \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/package-info.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/package-info.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/package-info.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011-2021 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. + */ + +/** + * Miscellaneous utility classes, such as loggers, tuples or queue suppliers and implementations. + */ +@NonNullApi +package reactor.util; + +import reactor.util.annotation.NonNullApi; +import javax.annotation.Nullable; \ No newline at end of file Index: 3rdParty_sources/reactor/reactor/util/retry/ImmutableRetrySignal.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/retry/ImmutableRetrySignal.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/retry/ImmutableRetrySignal.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020-2021 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.retry; + +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +/** + * An immutable {@link reactor.util.retry.Retry.RetrySignal} that can be used for retained + * copies of mutable implementations. + * + * @author Simon Baslé + */ +final class ImmutableRetrySignal implements Retry.RetrySignal { + + final long failureTotalIndex; + final long failureSubsequentIndex; + final Throwable failure; + final ContextView retryContext; + + ImmutableRetrySignal(long failureTotalIndex, long failureSubsequentIndex, + Throwable failure) { + this(failureTotalIndex, failureSubsequentIndex, failure, Context.empty()); + } + + ImmutableRetrySignal(long failureTotalIndex, long failureSubsequentIndex, + Throwable failure, ContextView retryContext) { + this.failureTotalIndex = failureTotalIndex; + this.failureSubsequentIndex = failureSubsequentIndex; + this.failure = failure; + this.retryContext = retryContext; + } + + @Override + public long totalRetries() { + return this.failureTotalIndex; + } + + @Override + public long totalRetriesInARow() { + return this.failureSubsequentIndex; + } + + @Override + public Throwable failure() { + return this.failure; + } + + @Override + public ContextView retryContextView() { + return retryContext; + } + + @Override + public Retry.RetrySignal copy() { + return this; + } + + @Override + public String toString() { + return "attempt #" + (failureTotalIndex + 1) + " (" + (failureSubsequentIndex + 1) + " in a row), last failure={" + failure + '}'; + } +} Index: 3rdParty_sources/reactor/reactor/util/retry/Retry.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/retry/Retry.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/retry/Retry.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2020-2021 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.retry; + +import java.time.Duration; +import java.util.function.Function; + +import org.reactivestreams.Publisher; + +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Schedulers; +import reactor.util.context.Context; +import reactor.util.context.ContextView; + +import static reactor.util.retry.RetrySpec.*; + +/** + * Base abstract class for a strategy to decide when to retry given a companion {@link Flux} of {@link RetrySignal}, + * for use with {@link Flux#retryWhen(Retry)} and {@link reactor.core.publisher.Mono#retryWhen(Retry)}. + * Also provides access to configurable built-in strategies via static factory methods: + *
    + *
  • {@link #indefinitely()}
  • + *
  • {@link #max(long)}
  • + *
  • {@link #maxInARow(long)}
  • + *
  • {@link #fixedDelay(long, Duration)}
  • + *
  • {@link #backoff(long, Duration)}
  • + *
+ *

+ * Users are encouraged to provide either concrete custom {@link Retry} strategies or builders that produce + * such concrete {@link Retry}. The {@link RetrySpec} returned by e.g. {@link #max(long)} is a good inspiration + * for a fluent approach that generates a {@link Retry} at each step and uses immutability/copy-on-write to enable + * sharing of intermediate steps (that can thus be considered templates). + * + * @author Simon Baslé + */ +public abstract class Retry { + + public final ContextView retryContext; + + public Retry() { + this(Context.empty()); + } + + protected Retry(ContextView retryContext) { + this.retryContext = retryContext; + } + + /** + * Generates the companion publisher responsible for reacting to incoming {@link RetrySignal} emissions, effectively + * deciding when to retry. + *

+ * When the source signals an error, that {@link org.reactivestreams.Subscriber#onError(Throwable) onError} signal + * will be suppressed. Its {@link Throwable} will instead be attached to a {@link RetrySignal}, immediately emitted + * on the {@code retrySignals} publisher. Right after that emission, + * {@link org.reactivestreams.Subscription#request(long) request(1)} is called on the companion publisher. + *

+ * The response to that request decides if a retry should be made. Thus, the outer publisher will wait until a signal + * is emitted by the companion publisher, making it possible to delay retry attempts. + *

+ * Any + * {@link org.reactivestreams.Subscriber#onNext(Object) onNext} emitted by the companion publisher triggers a retry, + * {@link org.reactivestreams.Subscriber#onError(Throwable) onError} will fail the outer publisher and + * {@link org.reactivestreams.Subscriber#onComplete() onComplete} will complete the outer publisher (effectively + * suppressing the original error/{@link Throwable}). + *

+ * As an example, the simplest form of retry companion would be to return the incoming {@link Flux} of {@link RetrySignal} + * without modification. This would render a retry strategy that immediately retries, forever. + * + * @param retrySignals the errors from the outer publisher as {@link RetrySignal} objects, + * containing the {@link Throwable} causing the error as well as retry counter metadata. + * @return the companion publisher responsible for reacting to incoming {@link RetrySignal} emissions, + * effectively deciding when to retry. + */ + public abstract Publisher generateCompanion(Flux retrySignals); + + /** + * Return the user provided context that was set at construction time. + * + * @return the user provided context that will be accessible via {@link RetrySignal#retryContextView()}. + */ + public ContextView retryContext() { + return retryContext; + } + + + /** + * State used in {@link Flux#retryWhen(Retry)} and {@link reactor.core.publisher.Mono#retryWhen(Retry)}, + * providing the {@link Throwable} that caused the source to fail as well as counters keeping track of retries. + */ + public interface RetrySignal { + + /** + * The total number of retries since the source first was subscribed to (in other words the number of errors -1 + * since the source was first subscribed to). + * + * @return the total number of retries since the source first was subscribed to. + */ + long totalRetries(); + + /** + * Retry counter resetting after each {@link org.reactivestreams.Subscriber#onNext(Object) onNext} (in other + * words the number of errors -1 since the latest {@link org.reactivestreams.Subscriber#onNext(Object) onNext}). + * + * @return the number of retries since the latest {@link org.reactivestreams.Subscriber#onNext(Object) onNext}, + * or the number of retries since the source first was subscribed to if there hasn't been any + * {@link org.reactivestreams.Subscriber#onNext(Object) onNext} signals (in which case + * {@link RetrySignal#totalRetries()} and {@link RetrySignal#totalRetriesInARow()} are equivalent). + */ + long totalRetriesInARow(); + + /** + * The {@link Throwable} that caused the current {@link org.reactivestreams.Subscriber#onError(Throwable) onError} signal. + * + * @return the current failure. + */ + Throwable failure(); + + /** + * Return a read-only view of the user provided context, which may be used to store + * objects to be reset/rolled-back or otherwise mutated before or after a retry. + * + * @return a read-only view of a user provided context. + */ + default ContextView retryContextView() { + return Context.empty(); + } + + /** + * An immutable copy of this {@link RetrySignal} which is guaranteed to give a consistent view + * of the state at the time at which this method is invoked. + * This is especially useful if this {@link RetrySignal} is a transient view of the state of the underlying + * retry subscriber. + * + * @return an immutable copy of the current {@link RetrySignal}, always safe to use + */ + default RetrySignal copy() { + return new ImmutableRetrySignal(totalRetries(), totalRetriesInARow(), failure(), retryContextView()); + } + } + + /** + * A {@link RetryBackoffSpec} preconfigured for exponential backoff strategy with jitter, given a maximum number of retry attempts + * and a minimum {@link Duration} for the backoff. + *

+ * + * + * @param maxAttempts the maximum number of retry attempts to allow + * @param minBackoff the minimum {@link Duration} for the first backoff + * @return the exponential backoff spec for further configuration + * @see RetryBackoffSpec#maxAttempts(long) + * @see RetryBackoffSpec#minBackoff(Duration) + */ + public static RetryBackoffSpec backoff(long maxAttempts, Duration minBackoff) { + return new RetryBackoffSpec(Context.empty(), maxAttempts, t -> true, false, minBackoff, MAX_BACKOFF, 0.5d, Schedulers::parallel, + NO_OP_CONSUMER, NO_OP_CONSUMER, NO_OP_BIFUNCTION, NO_OP_BIFUNCTION, + RetryBackoffSpec.BACKOFF_EXCEPTION_GENERATOR); + } + + /** + * A {@link RetryBackoffSpec} preconfigured for fixed delays (min backoff equals max backoff, no jitter), given a maximum number of retry attempts + * and the fixed {@link Duration} for the backoff. + *

+ * + *

+ * Note that calling {@link RetryBackoffSpec#minBackoff(Duration)} or {@link RetryBackoffSpec#maxBackoff(Duration)} would switch + * back to an exponential backoff strategy. + * + * @param maxAttempts the maximum number of retry attempts to allow + * @param fixedDelay the {@link Duration} of the fixed delays + * @return the fixed delays spec for further configuration + * @see RetryBackoffSpec#maxAttempts(long) + * @see RetryBackoffSpec#minBackoff(Duration) + * @see RetryBackoffSpec#maxBackoff(Duration) + */ + public static RetryBackoffSpec fixedDelay(long maxAttempts, Duration fixedDelay) { + return new RetryBackoffSpec(Context.empty(), maxAttempts, t -> true, false, fixedDelay, fixedDelay, 0d, Schedulers::parallel, + NO_OP_CONSUMER, NO_OP_CONSUMER, NO_OP_BIFUNCTION, NO_OP_BIFUNCTION, + RetryBackoffSpec.BACKOFF_EXCEPTION_GENERATOR); + } + + /** + * A {@link RetrySpec} preconfigured for a simple strategy with maximum number of retry attempts. + *

+ * + * + * @param max the maximum number of retry attempts to allow + * @return the max attempt spec for further configuration + * @see RetrySpec#maxAttempts(long) + */ + public static RetrySpec max(long max) { + return new RetrySpec(Context.empty(), max, t -> true, false, NO_OP_CONSUMER, NO_OP_CONSUMER, NO_OP_BIFUNCTION, NO_OP_BIFUNCTION, + RetrySpec.RETRY_EXCEPTION_GENERATOR); + } + + /** + * A {@link RetrySpec} preconfigured for a simple strategy with maximum number of retry attempts over + * subsequent transient errors. An {@link org.reactivestreams.Subscriber#onNext(Object)} between + * errors resets the counter (see {@link RetrySpec#transientErrors(boolean)}). + *

+ * + * + * @param maxInARow the maximum number of retry attempts to allow in a row, reset by successful onNext + * @return the max in a row spec for further configuration + * @see RetrySpec#maxAttempts(long) + * @see RetrySpec#transientErrors(boolean) + */ + public static RetrySpec maxInARow(long maxInARow) { + return new RetrySpec(Context.empty(), maxInARow, t -> true, true, NO_OP_CONSUMER, NO_OP_CONSUMER, NO_OP_BIFUNCTION, NO_OP_BIFUNCTION, + RETRY_EXCEPTION_GENERATOR); + } + + /** + * A {@link RetrySpec} preconfigured for the most simplistic retry strategy: retry immediately and indefinitely + * (similar to {@link Flux#retry()}). + * + * @return the retry indefinitely spec for further configuration + */ + public static RetrySpec indefinitely() { + return new RetrySpec(Context.empty(), Long.MAX_VALUE, t -> true, false, NO_OP_CONSUMER, NO_OP_CONSUMER, NO_OP_BIFUNCTION, NO_OP_BIFUNCTION, + RetrySpec.RETRY_EXCEPTION_GENERATOR); + } + + /** + * A wrapper around {@link Function} to provide {@link Retry} by using lambda expressions. + * + * @param function the {@link Function} representing the desired {@link Retry} strategy as a lambda + * @return the {@link Retry} strategy adapted from the {@link Function} + */ + public static final Retry from(Function, ? extends Publisher> function) { + return new Retry(Context.empty()) { + @Override + public Publisher generateCompanion(Flux retrySignalCompanion) { + return function.apply(retrySignalCompanion); + } + }; + } + + /** + * An adapter for {@link Flux} of {@link Throwable}-based {@link Function} to provide {@link Retry} + * from a legacy retryWhen {@link Function}. + * + * @param function the {@link Function} representing the desired {@link Retry} strategy as a lambda + * @return the {@link Retry} strategy adapted from the {@link Function} + */ + public static final Retry withThrowable(Function, ? extends Publisher> function) { + return new Retry(Context.empty()) { + @Override + public Publisher generateCompanion(Flux retrySignals) { + return function.apply(retrySignals.map(RetrySignal::failure)); + } + }; + } + +} Index: 3rdParty_sources/reactor/reactor/util/retry/RetryBackoffSpec.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/retry/RetryBackoffSpec.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/retry/RetryBackoffSpec.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2020-2021 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.retry; + +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import reactor.core.Exceptions; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; +import reactor.util.annotation.Nullable; +import reactor.util.context.ContextView; + +/** + * A {@link Retry} strategy based on exponential backoffs, with configurable features. Use {@link Retry#backoff(long, Duration)} + * to obtain a preconfigured instance to start with. + *

+ * Retry delays are randomized with a user-provided {@link #jitter(double)} factor between {@code 0.d} (no jitter) + * and {@code 1.0} (default is {@code 0.5}). + * Even with the jitter, the effective backoff delay cannot be less than {@link #minBackoff(Duration)} + * nor more than {@link #maxBackoff(Duration)}. The delays and subsequent attempts are executed on the + * provided backoff {@link #scheduler(Scheduler)}. Alternatively, {@link Retry#fixedDelay(long, Duration)} provides + * a strategy where the min and max backoffs are the same and jitters are deactivated. + *

+ * 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} + * 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 + * had its own backoff, one can choose to set {@link #transientErrors(boolean)} to {@code true}. + * The comparison to {@link #maxAttempts(long)} will then be done with the number of subsequent attempts + * that failed without an {@link org.reactivestreams.Subscriber#onNext(Object) onNext} in between. + *

+ * The {@link RetryBackoffSpec} is copy-on-write and as such can be stored as a "template" and further configured + * by different components without a risk of modifying the original configuration. + * + * @author Simon Baslé + */ +public final class RetryBackoffSpec extends Retry { + + static final BiFunction BACKOFF_EXCEPTION_GENERATOR = (builder, rs) -> + Exceptions.retryExhausted("Retries exhausted: " + ( + builder.isTransientErrors + ? rs.totalRetriesInARow() + "/" + builder.maxAttempts + " in a row (" + rs.totalRetries() + " total)" + : rs.totalRetries() + "/" + builder.maxAttempts + ), rs.failure()); + + /** + * The configured minimum backoff {@link Duration}. + * @see #minBackoff(Duration) + */ + public final Duration minBackoff; + + /** + * The configured maximum backoff {@link Duration}. + * @see #maxBackoff(Duration) + */ + public final Duration maxBackoff; + + /** + * The configured jitter factor, as a {@code double}. + * @see #jitter(double) + */ + public final double jitterFactor; + + /** + * The configured {@link Supplier} of {@link Scheduler} on which to execute backoffs. + * @see #scheduler(Scheduler) + */ + public final Supplier backoffSchedulerSupplier; + + /** + * The configured maximum for retry attempts. + * + * @see #maxAttempts(long) + */ + public final long maxAttempts; + + /** + * The configured {@link Predicate} to filter which exceptions to retry. + * @see #filter(Predicate) + * @see #modifyErrorFilter(Function) + */ + public final Predicate errorFilter; + + /** + * The configured transient error handling flag. + * @see #transientErrors(boolean) + */ + public final boolean isTransientErrors; + + final Consumer syncPreRetry; + final Consumer syncPostRetry; + final BiFunction, Mono> asyncPreRetry; + final BiFunction, Mono> asyncPostRetry; + + final BiFunction retryExhaustedGenerator; + + /** + * Copy constructor. + */ + RetryBackoffSpec( + ContextView retryContext, + long max, + Predicate aThrowablePredicate, + boolean isTransientErrors, + Duration minBackoff, Duration maxBackoff, double jitterFactor, + Supplier backoffSchedulerSupplier, + Consumer doPreRetry, + Consumer doPostRetry, + BiFunction, Mono> asyncPreRetry, + BiFunction, Mono> asyncPostRetry, + BiFunction retryExhaustedGenerator) { + super(retryContext); + this.maxAttempts = max; + this.errorFilter = aThrowablePredicate::test; //massaging type + this.isTransientErrors = isTransientErrors; + this.minBackoff = minBackoff; + this.maxBackoff = maxBackoff; + this.jitterFactor = jitterFactor; + this.backoffSchedulerSupplier = backoffSchedulerSupplier; + this.syncPreRetry = doPreRetry; + this.syncPostRetry = doPostRetry; + this.asyncPreRetry = asyncPreRetry; + this.asyncPostRetry = asyncPostRetry; + this.retryExhaustedGenerator = retryExhaustedGenerator; + } + + /** + * Set the user provided {@link Retry#retryContext() context} that can be used to manipulate state on retries. + * + * @param retryContext a new snapshot of user provided data + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec withRetryContext(ContextView retryContext) { + return new RetryBackoffSpec( + retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Set the maximum number of retry attempts allowed. 1 meaning "1 retry attempt": + * the original subscription plus an extra re-subscription in case of an error, but + * no more. + * + * @param maxAttempts the new retry attempt limit + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec maxAttempts(long maxAttempts) { + return new RetryBackoffSpec( + this.retryContext, + maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Set the {@link Predicate} that will filter which errors can be retried. Exceptions + * that don't pass the predicate will be propagated downstream and terminate the retry + * sequence. Defaults to allowing retries for all exceptions. + * + * @param errorFilter the predicate to filter which exceptions can be retried + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec filter(Predicate errorFilter) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + Objects.requireNonNull(errorFilter, "errorFilter"), + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Allows to augment a previously {@link #filter(Predicate) set} {@link Predicate} with + * a new condition to allow retries of some exception or not. This can typically be used with + * {@link Predicate#and(Predicate)} to combine existing predicate(s) with a new one. + *

+ * For example: + *


+	 * //given
+	 * RetrySpec retryTwiceIllegalArgument = Retry.max(2)
+	 *     .filter(e -> e instanceof IllegalArgumentException);
+	 *
+	 * RetrySpec retryTwiceIllegalArgWithCause = retryTwiceIllegalArgument.modifyErrorFilter(old ->
+	 *     old.and(e -> e.getCause() != null));
+	 * 
+ * + * @param predicateAdjuster a {@link Function} that returns a new {@link Predicate} given the + * currently in place {@link Predicate} (usually deriving from the old predicate). + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec modifyErrorFilter( + Function, Predicate> predicateAdjuster) { + Objects.requireNonNull(predicateAdjuster, "predicateAdjuster"); + Predicate newPredicate = Objects.requireNonNull(predicateAdjuster.apply(this.errorFilter), + "predicateAdjuster must return a new predicate"); + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + newPredicate, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Add synchronous behavior to be executed before the retry trigger is emitted in + * the companion publisher. This should not be blocking, as the companion publisher + * might be executing in a shared thread. + * + * @param doBeforeRetry the synchronous hook to execute before retry trigger is emitted + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + * @see #doBeforeRetryAsync(Function) andDelayRetryWith for an asynchronous version + */ + public RetryBackoffSpec doBeforeRetry( + Consumer doBeforeRetry) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry.andThen(doBeforeRetry), + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Add synchronous behavior to be executed after the retry trigger is emitted in + * the companion publisher. This should not be blocking, as the companion publisher + * might be publishing events in a shared thread. + * + * @param doAfterRetry the synchronous hook to execute after retry trigger is started + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + * @see #doAfterRetryAsync(Function) andRetryThen for an asynchronous version + */ + public RetryBackoffSpec doAfterRetry(Consumer doAfterRetry) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry.andThen(doAfterRetry), + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Add asynchronous behavior to be executed before the current retry trigger in the companion publisher, + * thus delaying the resulting retry trigger with the additional {@link Mono}. + * + * @param doAsyncBeforeRetry the asynchronous hook to execute before original retry trigger is emitted + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec doBeforeRetryAsync( + Function> doAsyncBeforeRetry) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + (rs, m) -> asyncPreRetry.apply(rs, m).then(doAsyncBeforeRetry.apply(rs)), + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Add asynchronous behavior to be executed after the current retry trigger in the companion publisher, + * thus delaying the resulting retry trigger with the additional {@link Mono}. + * + * @param doAsyncAfterRetry the asynchronous hook to execute after original retry trigger is emitted + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec doAfterRetryAsync( + Function> doAsyncAfterRetry) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + (rs, m) -> asyncPostRetry.apply(rs, m).then(doAsyncAfterRetry.apply(rs)), + this.retryExhaustedGenerator); + } + + /** + * 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. + * + * + * @param retryExhaustedGenerator the {@link Function} that generates the {@link Throwable} for the last + * {@link reactor.util.retry.Retry.RetrySignal} + * @return a new copy of the {@link RetryBackoffSpec} which can either be further + * configured or used as {@link reactor.util.retry.Retry} + */ + public RetryBackoffSpec onRetryExhaustedThrow(BiFunction retryExhaustedGenerator) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + Objects.requireNonNull(retryExhaustedGenerator, "retryExhaustedGenerator")); + } + + /** + * 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()}. + * 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. + *

+ * For a backoff based retry, the backoff is also computed based on the index within + * the burst, meaning the next error after a recovery will be retried with a {@link #minBackoff(Duration)} delay. + * + * @param isTransientErrors {@code true} to activate transient mode + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec transientErrors(boolean isTransientErrors) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Set the minimum {@link Duration} for the first backoff. This method switches to an + * exponential backoff strategy if not already done so. Defaults to {@link Duration#ZERO} + * when the strategy was initially not a backoff one. + * + * @param minBackoff the minimum backoff {@link Duration} + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec minBackoff(Duration minBackoff) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + minBackoff, + this.maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Set a hard maximum {@link Duration} for exponential backoffs. This method switches + * to an exponential backoff strategy with a zero minimum backoff if not already a backoff + * strategy. Defaults to {@code Duration.ofMillis(Long.MAX_VALUE)}. + * + * @param maxBackoff the maximum backoff {@link Duration} + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec maxBackoff(Duration maxBackoff) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + maxBackoff, + this.jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Set a jitter factor for exponential backoffs that adds randomness to each backoff. This can + * be helpful in reducing cascading failure due to retry-storms. This method switches to an + * exponential backoff strategy with a zero minimum backoff if not already a backoff strategy. + * Defaults to {@code 0.5} (a jitter of at most 50% of the computed delay). + * + * @param jitterFactor the new jitter factor as a {@code double} between {@code 0d} and {@code 1d} + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec jitter(double jitterFactor) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + jitterFactor, + this.backoffSchedulerSupplier, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Set a {@link Scheduler} on which to execute the delays computed by the exponential backoff + * strategy. Defaults to a deferred resolution of the current {@link Schedulers#parallel()} (use + * {@code null} to reset to this default). + * + * @param backoffScheduler the {@link Scheduler} to use, or {@code null} to revert to the default + * @return a new copy of the {@link RetryBackoffSpec} which can either be further configured or used as {@link Retry} + */ + public RetryBackoffSpec scheduler(@Nullable Scheduler backoffScheduler) { + return new RetryBackoffSpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.minBackoff, + this.maxBackoff, + this.jitterFactor, + backoffScheduler == null ? Schedulers::parallel : () -> backoffScheduler, + this.syncPreRetry, + this.syncPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + //========== + // strategy + //========== + + protected void validateArguments() { + if (jitterFactor < 0 || jitterFactor > 1) throw new IllegalArgumentException("jitterFactor must be between 0 and 1 (default 0.5)"); + } + + @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(); + + 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 (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) { + 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); + } + + 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 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); + }); + } +} Index: 3rdParty_sources/reactor/reactor/util/retry/RetrySpec.java =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/retry/RetrySpec.java (revision 0) +++ 3rdParty_sources/reactor/reactor/util/retry/RetrySpec.java (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2020-2021 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.retry; + +import java.time.Duration; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +import reactor.core.Exceptions; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.util.context.ContextView; + +/** + * A simple count-based {@link Retry} strategy with configurable features. Use {@link Retry#max(long)}, + * {@link Retry#maxInARow(long)} or {@link Retry#indefinitely()} to obtain a preconfigured instance to start with. + *

+ * 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} + * 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 + * had its own attempt counter, one can choose to set {@link #transientErrors(boolean)} to {@code true}. + * The comparison to {@link #maxAttempts(long)} will then be done with the number of subsequent attempts + * that failed without an {@link org.reactivestreams.Subscriber#onNext(Object) onNext} in between. + *

+ * The {@link RetrySpec} is copy-on-write and as such can be stored as a "template" and further configured + * by different components without a risk of modifying the original configuration. + * + * @author Simon Baslé + */ +public final class RetrySpec extends Retry { + + static final Duration MAX_BACKOFF = Duration.ofMillis(Long.MAX_VALUE); + static final Consumer NO_OP_CONSUMER = rs -> {}; + static final BiFunction, Mono> NO_OP_BIFUNCTION = (rs, m) -> m; + + + static final BiFunction + RETRY_EXCEPTION_GENERATOR = (builder, rs) -> + Exceptions.retryExhausted("Retries exhausted: " + ( + builder.isTransientErrors + ? rs.totalRetriesInARow() + "/" + builder.maxAttempts + " in a row (" + rs.totalRetries() + " total)" + : rs.totalRetries() + "/" + builder.maxAttempts + ), rs.failure()); + + /** + * The configured maximum for retry attempts. + * + * @see #maxAttempts(long) + */ + public final long maxAttempts; + + /** + * The configured {@link Predicate} to filter which exceptions to retry. + * @see #filter(Predicate) + * @see #modifyErrorFilter(Function) + */ + public final Predicate errorFilter; + + /** + * The configured transient error handling flag. + * @see #transientErrors(boolean) + */ + public final boolean isTransientErrors; + + final Consumer doPreRetry; + final Consumer doPostRetry; + final BiFunction, Mono> asyncPreRetry; + final BiFunction, Mono> asyncPostRetry; + + final BiFunction retryExhaustedGenerator; + + /** + * Copy constructor. + */ + RetrySpec(ContextView retryContext, + long max, + Predicate aThrowablePredicate, + boolean isTransientErrors, + Consumer doPreRetry, + Consumer doPostRetry, + BiFunction, Mono> asyncPreRetry, + BiFunction, Mono> asyncPostRetry, + BiFunction retryExhaustedGenerator) { + super(retryContext); + this.maxAttempts = max; + this.errorFilter = aThrowablePredicate::test; //massaging type + this.isTransientErrors = isTransientErrors; + this.doPreRetry = doPreRetry; + this.doPostRetry = doPostRetry; + this.asyncPreRetry = asyncPreRetry; + this.asyncPostRetry = asyncPostRetry; + this.retryExhaustedGenerator = retryExhaustedGenerator; + } + + /** + * Set the user provided {@link Retry#retryContext() context} that can be used to manipulate state on retries. + * + * @param retryContext a new snapshot of user provided data + * @return a new copy of the {@link RetrySpec} which can either be further configured or used as {@link Retry} + */ + public RetrySpec withRetryContext(ContextView retryContext) { + return new RetrySpec( + retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.doPreRetry, + this.doPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Set the maximum number of retry attempts allowed. 1 meaning "1 retry attempt": + * the original subscription plus an extra re-subscription in case of an error, but + * no more. + * + * @param maxAttempts the new retry attempt limit + * @return a new copy of the {@link RetrySpec} which can either be further configured or used as a {@link Retry} + */ + public RetrySpec maxAttempts(long maxAttempts) { + return new RetrySpec( + this.retryContext, + maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.doPreRetry, + this.doPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Set the {@link Predicate} that will filter which errors can be retried. Exceptions + * that don't pass the predicate will be propagated downstream and terminate the retry + * sequence. Defaults to allowing retries for all exceptions. + * + * @param errorFilter the predicate to filter which exceptions can be retried + * @return a new copy of the {@link RetrySpec} which can either be further configured or used as {@link Retry} + */ + public RetrySpec filter(Predicate errorFilter) { + return new RetrySpec( + this.retryContext, + this.maxAttempts, + Objects.requireNonNull(errorFilter, "errorFilter"), + this.isTransientErrors, + this.doPreRetry, + this.doPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Allows to augment a previously {@link #filter(Predicate) set} {@link Predicate} with + * a new condition to allow retries of some exception or not. This can typically be used with + * {@link Predicate#and(Predicate)} to combine existing predicate(s) with a new one. + *

+ * For example: + *


+	 * //given
+	 * RetrySpec retryTwiceIllegalArgument = Retry.max(2)
+	 *     .filter(e -> e instanceof IllegalArgumentException);
+	 *
+	 * RetrySpec retryTwiceIllegalArgWithCause = retryTwiceIllegalArgument.modifyErrorFilter(old ->
+	 *     old.and(e -> e.getCause() != null));
+	 * 
+ * + * @param predicateAdjuster a {@link Function} that returns a new {@link Predicate} given the + * currently in place {@link Predicate} (usually deriving from the old predicate). + * @return a new copy of the {@link RetrySpec} which can either be further configured or used as {@link Retry} + */ + public RetrySpec modifyErrorFilter( + Function, Predicate> predicateAdjuster) { + Objects.requireNonNull(predicateAdjuster, "predicateAdjuster"); + Predicate newPredicate = Objects.requireNonNull(predicateAdjuster.apply(this.errorFilter), + "predicateAdjuster must return a new predicate"); + return new RetrySpec( + this.retryContext, + this.maxAttempts, + newPredicate, + this.isTransientErrors, + this.doPreRetry, + this.doPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Add synchronous behavior to be executed before the retry trigger is emitted in + * the companion publisher. This should not be blocking, as the companion publisher + * might be executing in a shared thread. + * + * @param doBeforeRetry the synchronous hook to execute before retry trigger is emitted + * @return a new copy of the {@link RetrySpec} which can either be further configured or used as {@link Retry} + * @see #doBeforeRetryAsync(Function) andDelayRetryWith for an asynchronous version + */ + public RetrySpec doBeforeRetry( + Consumer doBeforeRetry) { + return new RetrySpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.doPreRetry.andThen(doBeforeRetry), + this.doPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Add synchronous behavior to be executed after the retry trigger is emitted in + * the companion publisher. This should not be blocking, as the companion publisher + * might be publishing events in a shared thread. + * + * @param doAfterRetry the synchronous hook to execute after retry trigger is started + * @return a new copy of the {@link RetrySpec} which can either be further configured or used as {@link Retry} + * @see #doAfterRetryAsync(Function) andRetryThen for an asynchronous version + */ + public RetrySpec doAfterRetry(Consumer doAfterRetry) { + return new RetrySpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.doPreRetry, + this.doPostRetry.andThen(doAfterRetry), + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Add asynchronous behavior to be executed before the current retry trigger in the companion publisher, + * thus delaying the resulting retry trigger with the additional {@link Mono}. + * + * @param doAsyncBeforeRetry the asynchronous hook to execute before original retry trigger is emitted + * @return a new copy of the {@link RetrySpec} which can either be further configured or used as {@link Retry} + */ + public RetrySpec doBeforeRetryAsync( + Function> doAsyncBeforeRetry) { + return new RetrySpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.doPreRetry, + this.doPostRetry, + (rs, m) -> asyncPreRetry.apply(rs, m).then(doAsyncBeforeRetry.apply(rs)), + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + /** + * Add asynchronous behavior to be executed after the current retry trigger in the companion publisher, + * thus delaying the resulting retry trigger with the additional {@link Mono}. + * + * @param doAsyncAfterRetry the asynchronous hook to execute after original retry trigger is emitted + * @return a new copy of the {@link RetrySpec} which can either be further configured or used as {@link Retry} + */ + public RetrySpec doAfterRetryAsync( + Function> doAsyncAfterRetry) { + return new RetrySpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.doPreRetry, + this.doPostRetry, + this.asyncPreRetry, + (rs, m) -> asyncPostRetry.apply(rs, m).then(doAsyncAfterRetry.apply(rs)), + this.retryExhaustedGenerator); + } + + /** + * 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. + * + * @param retryExhaustedGenerator the {@link Function} that generates the {@link Throwable} for the last + * {@link reactor.util.retry.Retry.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) { + return new RetrySpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + this.isTransientErrors, + this.doPreRetry, + this.doPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + Objects.requireNonNull(retryExhaustedGenerator, "retryExhaustedGenerator")); + } + + /** + * 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()}. + * 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. + *

+ * In the case of a simple count-based retry, this means that the {@link #maxAttempts(long)} + * is applied to each burst individually. + * + * @param isTransientErrors {@code true} to activate transient mode + * @return a new copy of the {@link RetrySpec} which can either be further configured or used as {@link Retry} + */ + public RetrySpec transientErrors(boolean isTransientErrors) { + return new RetrySpec( + this.retryContext, + this.maxAttempts, + this.errorFilter, + isTransientErrors, + this.doPreRetry, + this.doPostRetry, + this.asyncPreRetry, + this.asyncPostRetry, + this.retryExhaustedGenerator); + } + + //========== + // strategy + //========== + + @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(); + + 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); + } + }); + } + + //=================== + // utility functions + //=================== + + static Mono applyHooks(RetrySignal copyOfSignal, + Mono originalCompanion, + final Consumer doPreRetry, + final Consumer doPostRetry, + final BiFunction, Mono> asyncPreRetry, + final BiFunction, Mono> asyncPostRetry) { + if (doPreRetry != NO_OP_CONSUMER) { + try { + doPreRetry.accept(copyOfSignal); + } + catch (Throwable e) { + return Mono.error(e); + } + } + + Mono postRetrySyncMono; + if (doPostRetry != NO_OP_CONSUMER) { + postRetrySyncMono = Mono.fromRunnable(() -> doPostRetry.accept(copyOfSignal)); + } + else { + postRetrySyncMono = Mono.empty(); + } + + 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); + } +} Index: 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecAttempts.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecAttempts.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecAttempts.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,4006 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + retryWhen(spec ) + + + + + subscribe() + + + + subscribe() + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + retryWhen(spec ) + + + + subscribe() + + subscribe() + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Retry.max(1) + ) + + + + + + + + + + + + + + + ! + + 0 + + 1 + spec = + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecBackoff.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecBackoff.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecBackoff.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,1756 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + subscribe() + + + + + subscribe() + + + + subscribe() + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ? + + + + ? + retryWhen(spec) + + + + + + jitter + + + + Retry.backoff(3,Duration.ofMillis(100)) + ) + + + + + + + + + + + + + + + + + + + + + + + + + ! + + ! + + 0 + + 1 + + 2 + spec = + + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecFixed.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecFixed.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecFixed.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,3399 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + subscribe() + + + + + + retryWhen(spec) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + + Retry.fixedDelay(2,Duration.ofMillis(100)) + ) + + + + + + + + + + + + + + + + + + + + + + + + + ! + + ! + + 0 + + 1 + + 2 + spec = + + + + + + + + + + + + + + + + subscribe() + + + + ! + + + + + + + + + + Index: 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecInARow.svg =================================================================== diff -u --- 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecInARow.svg (revision 0) +++ 3rdParty_sources/reactor/reactor/util/retry/doc-files/marbles/retrySpecInARow.svg (revision 03ee7b3a8cdecd210e54619377d06f9c7cb4014b) @@ -0,0 +1,2875 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscribe() + + + + subscribe() + + subscribe() + + + + + + + + retryWhen(spec) + + + + + + + + + + + + + + + Retry.maxInARow(1) + ) + + + + + + + + + + + + + + + ! + + 0 + + 1 + spec = + + + + + + + + + + + + + + + + subscribe() + + + + + + + subscribe() + + + + + 0 + -1 + 0 + -1 + 0 + 1 +