All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.driver.internal.shaded.reactor.core.publisher.Operators Maven / Gradle / Ivy

/*
 * Copyright (c) 2016-2022 VMware Inc. or its affiliates, All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package reactor.core.publisher;

import java.util.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_ASYNC_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_ASYNC_EMPTY) { setValue(v); //sync memory since setValue is non volatile if (STATE.compareAndSet(this, FUSED_ASYNC_EMPTY, FUSED_ASYNC_READY)) { Subscriber a = actual; a.onNext(null); 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_ASYNC_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_ASYNC_READY, FUSED_ASYNC_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_ASYNC_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 ASYNC fusion mode and is currently empty. */ static final int FUSED_ASYNC_EMPTY = 8; /** * Indicates this Subscription is in ASYNC fusion mode and has a value. */ static final int FUSED_ASYNC_READY = 16; /** * Indicates this Subscription is in ASYNC fusion mode and its value has been consumed. */ static final int FUSED_ASYNC_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); }