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

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

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

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

package reactor.core.publisher;

import java.util.Objects;
import java.util.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;
import reactor.util.context.ContextView;

/**
 * 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) {
		CoreSubscriber wrapped =
				Operators.restoreContextOnSubscriberIfAutoCPEnabled(this, actual);
		BaseSink sink = createSink(wrapped, backpressure);

		wrapped.onSubscribe(sink);
		try {
			source.accept(
					createMode == CreateMode.PUSH_PULL ? new SerializedFluxSink<>(sink) :
							sink);
		}
		catch (Throwable ex) {
			Exceptions.throwIfFatal(ex);
			sink.error(Operators.onOperatorError(ex, wrapped.currentContext()));
		}
	}

	@Override
	public Object scanUnsafe(Attr key) {
		if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC;
		return SourceProducer.super.scanUnsafe(key);
	}

	/**
	 * 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
		@Deprecated
		public Context currentContext() {
			return sink.currentContext();
		}

		@Override
		public ContextView contextView() {
			return sink.contextView();
		}

		@Override
		public FluxSink next(T t) {
			Objects.requireNonNull(t, "t is null in sink.next(t)");
			if (sink.isTerminated() || done) {
				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.onPushPullRequest(consumer);
			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
		@Deprecated
		public Context currentContext() {
			return sink.currentContext();
		}

		@Override
		public ContextView contextView() {
			return sink.contextView();
		}

		@Override
		public Object scanUnsafe(Attr key) {
			return serializedSink != null ? serializedSink.scanUnsafe(key) :
					baseSink.scanUnsafe(key);
		}

		@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();

		static final LongConsumer NOOP_CONSUMER = n -> {};

		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();
			REQUESTED.lazySet(this, Long.MIN_VALUE);
		}

		@Override
		@Deprecated
		public Context currentContext() {
			//we cache the context for hooks purposes, but this forces to go through the
			// chain when queried for context, in case downstream can update the Context...
			return actual.currentContext();
		}

		@Override
		public ContextView contextView() {
			//we cache the context for hooks purposes, but this forces to go through the
			// chain when queried for context, in case downstream can update the Context...
			return actual.currentContext();
		}

		@Override
		public void complete() {
			if (isTerminated()) {
				return;
			}
			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 & Long.MAX_VALUE;
		}

		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)) {
				long s = addCap(this, n);

				if (hasRequestConsumer(s)) {
					LongConsumer consumer = requestConsumer;
					if (!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");
			onPushRequest(consumer);
			return this;
		}

		protected void onPushRequest(LongConsumer initialRequestConsumer) {
			if (!REQUEST_CONSUMER.compareAndSet(this, null, NOOP_CONSUMER)) {
				throw new IllegalStateException(
						"A consumer has already been assigned to consume requests");
			}

			// do not change real flag since real consumer is technically absent
			initialRequestConsumer.accept(Long.MAX_VALUE);
		}

		protected void onPushPullRequest(LongConsumer requestConsumer) {
			if (!REQUEST_CONSUMER.compareAndSet(this, null, requestConsumer)) {
				throw new IllegalStateException(
						"A consumer has already been assigned to consume requests");
			}

			long initialRequest = markRequestConsumerSet(this);
			if (initialRequest > 0) {
				requestConsumer.accept(initialRequest);
			}
		}

		@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 requestedFromDownstream();
			if (key == Attr.RUN_STYLE) return Attr.RunStyle.ASYNC;

			return InnerProducer.super.scanUnsafe(key);
		}

		@Override
		public String toString() {
			return "FluxSink";
		}

		static  void produced(BaseSink instance, long toSub) {
			long s, r, u;
			do {
				s = instance.requested;
				r = s & Long.MAX_VALUE;
				if (r == 0 || r == Long.MAX_VALUE) {
					return;
				}
				u = Operators.subOrZero(r, toSub);
			} while (!REQUESTED.compareAndSet(instance, s, u | (s & Long.MIN_VALUE)));
		}


		static  long addCap(BaseSink instance, long toAdd) {
			long r, u, s;
			for (;;) {
				s = instance.requested;
				r = s & Long.MAX_VALUE;
				if (r == Long.MAX_VALUE) {
					return s;
				}
				u = Operators.addCap(r, toAdd);
				if (REQUESTED.compareAndSet(instance, s, u | (s & Long.MIN_VALUE))) {
					return s;
				}
			}
		}

		static  long markRequestConsumerSet(BaseSink instance) {
			long u, s;
			for (;;) {
				s = instance.requested;

				if (hasRequestConsumer(s)) {
					return s;
				}

				u = s & Long.MAX_VALUE;
				if (REQUESTED.compareAndSet(instance, s, u)) {
					return u;
				}
			}
		}

		static boolean hasRequestConsumer(long requestedState) {
			return (requestedState & Long.MIN_VALUE) == 0;
		}
	}

	static final class IgnoreSink extends BaseSink {

		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 s = requested;
				long r = s & Long.MAX_VALUE;
				if (r == 0L || REQUESTED.compareAndSet(this, s, (r - 1) | (s & Long.MIN_VALUE))) {
					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 (requestedFromDownstream() != 0) {
				actual.onNext(t);
				produced(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 = requestedFromDownstream();
				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) {
					produced(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 = requestedFromDownstream();
				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) {
					produced(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();
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy