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

reactor.core.publisher.MonoCacheTime 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.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.scheduler.Scheduler;
import reactor.util.Logger;
import reactor.util.Loggers;
import reactor.util.annotation.Nullable;

/**
 * An operator that caches the value from a source Mono with a TTL, after which the value
 * expires and the next subscription will trigger a new source subscription.
 *
 * @author Simon Baslé
 */
class MonoCacheTime extends MonoOperator implements Runnable {

	private static final Logger LOGGER = Loggers.getLogger(MonoCacheTime.class);

	final Duration ttl;
	final Scheduler clock;

	volatile Signal state;
	static final AtomicReferenceFieldUpdater STATE =
			AtomicReferenceFieldUpdater.newUpdater(MonoCacheTime.class, Signal.class, "state");

	static final Signal EMPTY = new ImmutableSignal<>(SignalType.ON_NEXT, null, null, null);

	MonoCacheTime(Mono source, Duration ttl, Scheduler clock) {
		super(source);
		this.ttl = ttl;
		this.clock = clock;
		//noinspection unchecked
		this.state = (Signal) EMPTY;
	}

	public void run() {
		LOGGER.debug("expired {}", state);
		//noinspection unchecked
		state = (Signal) EMPTY;
	}

	@Override
	public void subscribe(CoreSubscriber actual) {
		for(;;){
			Signal state = this.state;
			if (state == EMPTY) {
				//init or expired
				CoordinatorSubscriber newState = new CoordinatorSubscriber<>(this);
				if (STATE.compareAndSet(this, EMPTY, newState)) {
					source.subscribe(newState);
					CacheMonoSubscriber inner = new CacheMonoSubscriber<>(actual, newState);
					if (newState.add(inner)) {
						actual.onSubscribe(inner);
						break;
					}
				}
			}
			else if (state instanceof CoordinatorSubscriber) {
				//subscribed to source once, but not yet valued / cached
				CoordinatorSubscriber coordinator = (CoordinatorSubscriber) state;

				CacheMonoSubscriber inner = new CacheMonoSubscriber<>(actual, coordinator);
				if (coordinator.add(inner)) {
					actual.onSubscribe(inner);
					break;
				}
			}
			else {
				//state is an actual signal, cached
				if (state.isOnNext()) {
					actual.onSubscribe(new Operators.ScalarSubscription<>(actual, state.get()));
				}
				else if (state.isOnComplete()) {
					Operators.complete(actual);
				}
				else {
					Operators.error(actual, state.getThrowable());
				}
				break;
			}
		}
	}

	static final class CoordinatorSubscriber implements InnerConsumer, Signal {

		final MonoCacheTime main;

		volatile Subscription subscription;
		static final AtomicReferenceFieldUpdater S =
				AtomicReferenceFieldUpdater.newUpdater(CoordinatorSubscriber.class, Subscription.class, "subscription");

		volatile Operators.MonoSubscriber[] subscribers;
		static final AtomicReferenceFieldUpdater SUBSCRIBERS =
				AtomicReferenceFieldUpdater.newUpdater(CoordinatorSubscriber.class, Operators.MonoSubscriber[].class, "subscribers");

		CoordinatorSubscriber(MonoCacheTime main) {
			this.main = main;
			//noinspection unchecked
			this.subscribers = EMPTY;
		}

		/**
		 * unused in this context as the {@link Signal} interface is only
		 * implemented for use in the main's STATE compareAndSet.
		 */
		@Override
		public Throwable getThrowable() {
			throw new UnsupportedOperationException("illegal signal use");
		}

		/**
		 * unused in this context as the {@link Signal} interface is only
		 * implemented for use in the main's STATE compareAndSet.
		 */
		@Override
		public Subscription getSubscription() {
			throw new UnsupportedOperationException("illegal signal use");
		}

		/**
		 * unused in this context as the {@link Signal} interface is only
		 * implemented for use in the main's STATE compareAndSet.
		 */
		@Override
		public T get() {
			throw new UnsupportedOperationException("illegal signal use");
		}

		/**
		 * unused in this context as the {@link Signal} interface is only
		 * implemented for use in the main's STATE compareAndSet.
		 */
		@Override
		public SignalType getType() {
			throw new UnsupportedOperationException("illegal signal use");
		}

		final boolean add(Operators.MonoSubscriber toAdd) {
			for (; ; ) {
				Operators.MonoSubscriber[] a = subscribers;
				if (a == TERMINATED) {
					return false;
				}
				int n = a.length;
				//noinspection unchecked
				Operators.MonoSubscriber[] b = new Operators.MonoSubscriber[n + 1];
				System.arraycopy(a, 0, b, 0, n);
				b[n] = toAdd;
				if (SUBSCRIBERS.compareAndSet(this, a, b)) {
					return true;
				}
			}
		}

		final void remove(Operators.MonoSubscriber toRemove) {
			for (; ; ) {
				Operators.MonoSubscriber[] a = subscribers;
				if (a == TERMINATED || a == EMPTY) {
					return;
				}
				int n = a.length;
				int j = -1;
				for (int i = 0; i < n; i++) {
					if (a[i] == toRemove) {
						j = i;
						break;
					}
				}

				if (j < 0) {
					return;
				}

				Operators.MonoSubscriber[] b;
				if (n == 1) {
					b = EMPTY;
				}
				else {
					b = new Operators.MonoSubscriber[n - 1];
					System.arraycopy(a, 0, b, 0, j);
					System.arraycopy(a, j + 1, b, j, n - j - 1);
				}
				if (SUBSCRIBERS.compareAndSet(this, a, b)) {
					//no particular cleanup here for the EMPTY case, we don't cancel the
					// source because a new subscriber could come in before the coordinator
					// is terminated.

					return;
				}
			}
		}

		@Override
		public void onSubscribe(Subscription s) {
			if (Operators.validate(subscription, s)) {
				subscription = s;
				s.request(Long.MAX_VALUE);
			}
		}

		private void signalCached(Signal signal) {
			if (STATE.compareAndSet(main, this, signal)) {
				main.clock.schedule(main, main.ttl.toMillis(), TimeUnit.MILLISECONDS);
			}

			//noinspection unchecked
			for (Operators.MonoSubscriber inner : SUBSCRIBERS.getAndSet(this, TERMINATED)) {
				if (signal.isOnNext()) {
					inner.complete(signal.get());
				}
				else if (signal.isOnError()) {
					inner.onError(signal.getThrowable());
				}
				else {
					inner.onComplete();
				}
			}
		}

		@Override
		public void onNext(T t) {
			if (main.state != this) {
				Operators.onNextDroppedMulticast(t);
				return;
			}
			signalCached(Signal.next(t));
		}

		@Override
		public void onError(Throwable t) {
			if (main.state != this) {
				Operators.onErrorDroppedMulticast(t);
				return;
			}
			signalCached(Signal.error(t));
		}

		@Override
		public void onComplete() {
			signalCached(Signal.complete());
		}

		@Nullable
		@Override
		public Object scanUnsafe(Attr key) {
			return null;
		}

		private static final Operators.MonoSubscriber[] TERMINATED = new Operators.MonoSubscriber[0];
		private static final Operators.MonoSubscriber[] EMPTY = new Operators.MonoSubscriber[0];
	}

	static final class CacheMonoSubscriber extends Operators.MonoSubscriber {

		final CoordinatorSubscriber coordinator;

		CacheMonoSubscriber(CoreSubscriber actual, CoordinatorSubscriber coordinator) {
			super(actual);
			this.coordinator = coordinator;
		}

		@Override
		public void cancel() {
			super.cancel();
			coordinator.remove(this);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy