
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 extends T> 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 super T> 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 super T> actual, CoordinatorSubscriber coordinator) {
super(actual);
this.coordinator = coordinator;
}
@Override
public void cancel() {
super.cancel();
coordinator.remove(this);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy