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

reactor.core.publisher.FluxWindowWhen Maven / Gradle / Ivy

/*
 * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 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.HashSet;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
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.Exceptions;
import reactor.util.annotation.Nullable;

/**
 * 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 FluxOperator> {

	final Publisher start;

	final Function> end;

	final Supplier> drainQueueSupplier;

	final Supplier> processorQueueSupplier;

	FluxWindowWhen(Flux source,
			Publisher start,
			Function> end,
			Supplier> drainQueueSupplier,
			Supplier> processorQueueSupplier) {
		super(source);
		this.start = Objects.requireNonNull(start, "start");
		this.end = Objects.requireNonNull(end, "end");
		this.drainQueueSupplier =
				Objects.requireNonNull(drainQueueSupplier, "drainQueueSupplier");
		this.processorQueueSupplier =
				Objects.requireNonNull(processorQueueSupplier, "processorQueueSupplier");
	}

	@Override
	public int getPrefetch() {
		return Integer.MAX_VALUE;
	}

	@Override
	public void subscribe(CoreSubscriber> actual) {

		Queue q = drainQueueSupplier.get();

		WindowStartEndMainSubscriber main =
				new WindowStartEndMainSubscriber<>(actual, q, end, processorQueueSupplier);

		actual.onSubscribe(main);

		start.subscribe(main.starter);

		source.subscribe(main);
	}

	static final class WindowStartEndMainSubscriber
			implements InnerOperator>, Disposable {

		final CoreSubscriber> actual;

		final Queue queue;

		final WindowStartEndStarter starter;

		final Function> end;

		final Supplier> processorQueueSupplier;

		volatile long requested;
		@SuppressWarnings("rawtypes")
		static final AtomicLongFieldUpdater REQUESTED =
				AtomicLongFieldUpdater.newUpdater(WindowStartEndMainSubscriber.class,
						"requested");

		volatile int wip;
		@SuppressWarnings("rawtypes")
		static final AtomicIntegerFieldUpdater WIP =
				AtomicIntegerFieldUpdater.newUpdater(WindowStartEndMainSubscriber.class,
						"wip");

		volatile Subscription s;
		@SuppressWarnings("rawtypes")
		static final AtomicReferenceFieldUpdater
				S =
				AtomicReferenceFieldUpdater.newUpdater(WindowStartEndMainSubscriber.class,
						Subscription.class,
						"s");

		volatile int cancelled;
		@SuppressWarnings("rawtypes")
		static final AtomicIntegerFieldUpdater CANCELLED =
				AtomicIntegerFieldUpdater.newUpdater(WindowStartEndMainSubscriber.class,
						"cancelled");

		volatile int windowCount;
		@SuppressWarnings("rawtypes")
		static final AtomicIntegerFieldUpdater WINDOW_COUNT =
				AtomicIntegerFieldUpdater.newUpdater(WindowStartEndMainSubscriber.class,
						"windowCount");

		Set> windowEnds;

		Set> windows;

		volatile boolean done;

		volatile Throwable error;
		@SuppressWarnings("rawtypes")
		static final AtomicReferenceFieldUpdater
				ERROR = AtomicReferenceFieldUpdater.newUpdater(
				WindowStartEndMainSubscriber.class,
				Throwable.class,
				"error");

		WindowStartEndMainSubscriber(CoreSubscriber> actual,
				Queue queue,
				Function> end,
				Supplier> processorQueueSupplier) {
			this.actual = actual;
			this.queue = queue;
			this.starter = new WindowStartEndStarter<>(this);
			this.end = end;
			this.windowEnds = new HashSet<>();
			this.windows = new HashSet<>();
			this.processorQueueSupplier = processorQueueSupplier;
			WINDOW_COUNT.lazySet(this, 1);
		}

		@Override
		@Nullable
		public Object scanUnsafe(Attr key) {
			if (key == Attr.TERMINATED) return done;

			return InnerOperator.super.scanUnsafe(key);
		}

		@Override
		public CoreSubscriber> actual() {
			return actual;
		}

		@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) {
				queue.offer(t);
			}
			drain();
		}

		@Override
		public void onError(Throwable t) {
			if (Exceptions.addThrowable(ERROR, this, t)) {
				drain();
			}
			else {
				Operators.onErrorDropped(t, actual.currentContext());
			}
		}

		@Override
		public void onComplete() {
			if (done) {
				return;
			}
			closeMain(false);
			starter.cancel();
			done = true;

			drain();
		}

		@Override
		public void request(long n) {
			if (Operators.validate(n)) {
				Operators.addCap(REQUESTED, this, n);
			}
		}

		@Override
		public void cancel() {
			starter.cancel();
			closeMain(true);
		}

		void starterNext(U u) {
			NewWindow nw = new NewWindow<>(u);
			synchronized (this) {
				queue.offer(nw);
			}
			drain();
		}

		void starterError(Throwable e) {
			if (Exceptions.addThrowable(ERROR, this, e)) {
				drain();
			}
			else {
				Operators.onErrorDropped(e, actual.currentContext());
			}
		}

		void starterComplete() {
			if (done) {
				return;
			}
			closeMain(false);
			drain();
		}

		void endSignal(WindowStartEndEnder end) {
			remove(end);
			synchronized (this) {
				queue.offer(end);
			}
			drain();
		}

		void endError(Throwable e) {
			if (Exceptions.addThrowable(ERROR, this, e)) {
				drain();
			}
			else {
				Operators.onErrorDropped(e, actual.currentContext());
			}
		}

		void closeMain(boolean cancel) {
			if (cancel) {
				if (CANCELLED.compareAndSet(this, 0, 1)) {
					dispose();
				}
			} else {
				dispose();
			}
		}

		@Override
		public void dispose() {
			if (WINDOW_COUNT.decrementAndGet(this) == 0) {
				if (cancelled == 0)
					Operators.terminate(S, this);
				else
					s.cancel();
			}
		}

		@Override
		public boolean isDisposed() {
			return cancelled == 1 || done;
		}

		boolean add(WindowStartEndEnder ender) {
			synchronized (starter) {
				Set> set = windowEnds;
				if (set != null) {
					set.add(ender);
					return true;
				}
			}
			ender.cancel();
			return false;
		}

		void remove(WindowStartEndEnder ender) {
			synchronized (starter) {
				Set> set = windowEnds;
				if (set != null) {
					set.remove(ender);
				}
			}
		}

		void removeAll() {
			Set> set;
			synchronized (starter) {
				set = windowEnds;
				if (set == null) {
					return;
				}
				windowEnds = null;
			}

			for (Subscription s : set) {
				s.cancel();
			}
		}

		void drain() {
			if (WIP.getAndIncrement(this) != 0) {
				return;
			}

			final Subscriber> a = actual;
			final Queue q = queue;

			int missed = 1;

			for (; ; ) {

				for (; ; ) {
					Throwable e = error;
					if (e != null) {
						e = Exceptions.terminate(ERROR, this);
						if (e != Exceptions.TERMINATED) {
							Operators.terminate(S, this);
							starter.cancel();
							removeAll();

							for (UnicastProcessor w : windows) {
								w.onError(e);
							}
							windows = null;

							q.clear();

							a.onError(e);
						}

						return;
					}

					if (done || (windowCount == 0 && cancelled == 0)) {
						removeAll();

						for (UnicastProcessor w : windows) {
							w.onComplete();
						}
						windows = null;

						a.onComplete();
						return;
					}
					else if (windowCount == 0) {
						removeAll();
						dispose();
						return;
					}

					Object o = q.poll();

					if (o == null) {
						break;
					}

					if (o instanceof NewWindow) {
						if (cancelled == 0 && windowCount != 0 && !done) {
							@SuppressWarnings("unchecked") NewWindow newWindow =
									(NewWindow) o;

							Queue pq = processorQueueSupplier.get();

							Publisher p;

							try {
								p = Objects.requireNonNull(end.apply(newWindow.value),
										"The end returned a null publisher");
							}
							catch (Throwable ex) {
								Exceptions.addThrowable(ERROR,
										this,
										Operators.onOperatorError(s,
												ex,
												newWindow.value, actual.currentContext()));
								continue;
							}

							WINDOW_COUNT.getAndIncrement(this);

							UnicastProcessor w = new UnicastProcessor<>(pq, this);

							WindowStartEndEnder end =
									new WindowStartEndEnder<>(this, w);

							windows.add(w);

							if (add(end)) {

								long r = requested;
								if (r != 0L) {
									a.onNext(w);
									if (r != Long.MAX_VALUE) {
										REQUESTED.decrementAndGet(this);
									}
								}
								else {
									Exceptions.addThrowable(ERROR,
											this,
											Exceptions.failWithOverflow(
													"Could not emit window due to lack of requests"));
									continue;
								}

								p.subscribe(end);
							}
						}
					}
					else if (o instanceof WindowStartEndEnder) {
						@SuppressWarnings("unchecked") WindowStartEndEnder end =
								(WindowStartEndEnder) o;

						end.window.onComplete();
					}
					else {
						@SuppressWarnings("unchecked") T v = (T) o;

						for (UnicastProcessor w : windows) {
							w.onNext(v);
						}
					}
				}

				missed = WIP.addAndGet(this, -missed);
				if (missed == 0) {
					break;
				}
			}
		}
	}

	static final class WindowStartEndStarter
			extends Operators.DeferredSubscription implements CoreSubscriber {

		final WindowStartEndMainSubscriber main;

		WindowStartEndStarter(WindowStartEndMainSubscriber main) {
			this.main = main;
		}

		@Override
		public void onSubscribe(Subscription s) {
			if (set(s)) {
				s.request(Long.MAX_VALUE);
			}
		}

		@Override
		public void onNext(U t) {
			main.starterNext(t);
		}

		@Override
		public void onError(Throwable t) {
			main.starterError(t);
		}

		@Override
		public void onComplete() {
			main.starterComplete();
		}

	}

	static final class WindowStartEndEnder extends Operators.DeferredSubscription
			implements CoreSubscriber {

		final WindowStartEndMainSubscriber main;

		final UnicastProcessor window;

		WindowStartEndEnder(WindowStartEndMainSubscriber main,
				UnicastProcessor window) {
			this.main = main;
			this.window = window;
		}

		@Override
		public void onSubscribe(Subscription s) {
			if (set(s)) {
				s.request(Long.MAX_VALUE);
			}
		}

		@Override
		public void onNext(V t) {
			cancel();

			main.endSignal(this);
		}

		@Override
		public void onError(Throwable t) {
			main.endError(t);
		}

		@Override
		public void onComplete() {
			main.endSignal(this);
		}

	}

	static final class NewWindow {

		final U value;

		public NewWindow(U value) {
			this.value = value;
		}
	}
}