org.neo4j.driver.internal.shaded.reactor.core.publisher.ReplayProcessor Maven / Gradle / Ivy
Show all versions of neo4j-java-driver-all Show documentation
/*
* Copyright (c) 2016-2021 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.time.Duration;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.stream.Stream;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.Fuseable;
import reactor.core.Scannable;
import reactor.core.publisher.Sinks.EmitResult;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
import reactor.util.annotation.Nullable;
import reactor.util.concurrent.Queues;
import reactor.util.context.Context;
import static reactor.core.publisher.FluxReplay.ReplaySubscriber.EMPTY;
import static reactor.core.publisher.FluxReplay.ReplaySubscriber.TERMINATED;
/**
* Replays all or the last N items to Subscribers.
*
*
*
*
* @param the value type
* @deprecated To be removed in 3.5, prefer clear cut usage of {@link Sinks} through
* variations under {@link reactor.core.publisher.Sinks.MulticastReplaySpec Sinks.many().replay()}.
*/
@Deprecated
public final class ReplayProcessor extends FluxProcessor
implements Fuseable, InternalManySink {
/**
* Create a {@link ReplayProcessor} that caches the last element it has pushed,
* replaying it to late subscribers. This is a buffer-based ReplayProcessor with
* a history size of 1.
*
*
*
* @param the type of the pushed elements
*
* @return a new {@link ReplayProcessor} that replays its last pushed element to each new
* {@link Subscriber}
* @deprecated use {@link Sinks.MulticastReplaySpec#latest() Sinks.many().replay().latest()}
* (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5.
*/
@Deprecated
public static ReplayProcessor cacheLast() {
return cacheLastOrDefault(null);
}
/**
* Create a {@link ReplayProcessor} that caches the last element it has pushed,
* replaying it to late subscribers. If a {@link Subscriber} comes in before
* any value has been pushed, then the {@code defaultValue} is emitted instead.
* This is a buffer-based ReplayProcessor with a history size of 1.
*
*
*
* @param value a default value to start the sequence with in case nothing has been
* cached yet.
* @param the type of the pushed elements
*
* @return a new {@link ReplayProcessor} that replays its last pushed element to each new
* {@link Subscriber}, or a default one if nothing was pushed yet
* @deprecated use {@link Sinks.MulticastReplaySpec#latestOrDefault(Object) Sinks.many().replay().latestOrDefault(value)}
* (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5.
*/
@Deprecated
public static ReplayProcessor cacheLastOrDefault(@Nullable T value) {
ReplayProcessor b = create(1);
if (value != null) {
b.onNext(value);
}
return b;
}
/**
* Create a new {@link ReplayProcessor} that replays an unbounded number of elements,
* using a default internal {@link Queues#SMALL_BUFFER_SIZE Queue}.
*
* @param the type of the pushed elements
*
* @return a new {@link ReplayProcessor} that replays the whole history to each new
* {@link Subscriber}.
* @deprecated use {@link Sinks.MulticastReplaySpec#all() Sinks.many().replay().all()}
* (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5.
*/
@Deprecated
public static ReplayProcessor create() {
return create(Queues.SMALL_BUFFER_SIZE, true);
}
/**
* Create a new {@link ReplayProcessor} that replays up to {@code historySize}
* elements.
*
* @param historySize the backlog size, ie. maximum items retained for replay.
* @param the type of the pushed elements
*
* @return a new {@link ReplayProcessor} that replays a limited history to each new
* {@link Subscriber}.
* @deprecated use {@link Sinks.MulticastReplaySpec#limit(int) Sinks.many().replay().limit(historySize)}
* (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5.
*/
@Deprecated
public static ReplayProcessor create(int historySize) {
return create(historySize, false);
}
/**
* Create a new {@link ReplayProcessor} that either replay all the elements or a
* limited amount of elements depending on the {@code unbounded} parameter.
*
* @param historySize maximum items retained if bounded, or initial link size if unbounded
* @param unbounded true if "unlimited" data store must be supplied
* @param the type of the pushed elements
*
* @return a new {@link ReplayProcessor} that replays the whole history to each new
* {@link Subscriber} if configured as unbounded, a limited history otherwise.
* @deprecated use {@link Sinks.MulticastReplaySpec#limit(int) Sinks.many().replay().limit(historySize)}
* for bounded cases ({@code unbounded == false}) or {@link Sinks.MulticastReplaySpec#all(int) Sinks.many().replay().all(bufferSize)}
* otherwise (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5.
*/
@Deprecated
public static ReplayProcessor create(int historySize, boolean unbounded) {
FluxReplay.ReplayBuffer buffer;
if (unbounded) {
buffer = new FluxReplay.UnboundedReplayBuffer<>(historySize);
}
else {
buffer = new FluxReplay.SizeBoundReplayBuffer<>(historySize);
}
return new ReplayProcessor<>(buffer);
}
/**
* Creates a time-bounded replay processor.
*
* In this setting, the {@code ReplayProcessor} internally tags each observed item
* with a timestamp value supplied by the {@link Schedulers#parallel()} and keeps only
* those whose age is less than the supplied time value converted to milliseconds. For
* example, an item arrives at T=0 and the max age is set to 5; at T>=5 this first
* item is then evicted by any subsequent item or termination signal, leaving the
* buffer empty.
*
* Once the processor is terminated, subscribers subscribing to it will receive items
* that remained in the buffer after the terminal signal, regardless of their age.
*
* If an subscriber subscribes while the {@code ReplayProcessor} is active, it will
* observe only those items from within the buffer that have an age less than the
* specified time, and each item observed thereafter, even if the buffer evicts items
* due to the time constraint in the mean time. In other words, once an subscriber
* subscribes, it observes items without gaps in the sequence except for any outdated
* items at the beginning of the sequence.
*
*
* @param the type of items observed and emitted by the Processor
* @param maxAge the maximum age of the contained items
*
* @return a new {@link ReplayProcessor} that replays elements based on their age.
* @deprecated use {@link Sinks.MulticastReplaySpec#limit(Duration) Sinks.many().replay().limit(maxAge)}
* (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5.
*/
@Deprecated
public static ReplayProcessor createTimeout(Duration maxAge) {
return createTimeout(maxAge, Schedulers.parallel());
}
/**
* Creates a time-bounded replay processor.
*
* In this setting, the {@code ReplayProcessor} internally tags each observed item
* with a timestamp value supplied by the {@link Scheduler} and keeps only
* those whose age is less than the supplied time value converted to milliseconds. For
* example, an item arrives at T=0 and the max age is set to 5; at T>=5 this first
* item is then evicted by any subsequent item or termination signal, leaving the
* buffer empty.
*
* Once the processor is terminated, subscribers subscribing to it will receive items
* that remained in the buffer after the terminal signal, regardless of their age.
*
* If an subscriber subscribes while the {@code ReplayProcessor} is active, it will
* observe only those items from within the buffer that have an age less than the
* specified time, and each item observed thereafter, even if the buffer evicts items
* due to the time constraint in the mean time. In other words, once an subscriber
* subscribes, it observes items without gaps in the sequence except for any outdated
* items at the beginning of the sequence.
*
*
* @param the type of items observed and emitted by the Processor
* @param maxAge the maximum age of the contained items
*
* @return a new {@link ReplayProcessor} that replays elements based on their age.
* @deprecated use {@link Sinks.MulticastReplaySpec#limit(Duration, Scheduler) Sinks.many().replay().limit(maxAge, scheduler)}
* (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5.
*/
@Deprecated
public static ReplayProcessor createTimeout(Duration maxAge, Scheduler scheduler) {
return createSizeAndTimeout(Integer.MAX_VALUE, maxAge, scheduler);
}
/**
* Creates a time- and size-bounded replay processor.
*
* In this setting, the {@code ReplayProcessor} internally tags each received item
* with a timestamp value supplied by the {@link Schedulers#parallel()} and holds at
* most
* {@code size} items in its internal buffer. It evicts items from the start of the
* buffer if their age becomes less-than or equal to the supplied age in milliseconds
* or the buffer reaches its {@code size} limit.
*
* When subscribers subscribe to a terminated {@code ReplayProcessor}, they observe
* the items that remained in the buffer after the terminal signal, regardless of
* their age, but at most {@code size} items.
*
* If an subscriber subscribes while the {@code ReplayProcessor} is active, it will
* observe only those items from within the buffer that have age less than the
* specified time and each subsequent item, even if the buffer evicts items due to the
* time constraint in the mean time. In other words, once an subscriber subscribes, it
* observes items without gaps in the sequence except for the outdated items at the
* beginning of the sequence.
*
*
* @param the type of items observed and emitted by the Processor
* @param maxAge the maximum age of the contained items
* @param size the maximum number of buffered items
*
* @return a new {@link ReplayProcessor} that replay up to {@code size} elements, but
* will evict them from its history based on their age.
* @deprecated use {@link Sinks.MulticastReplaySpec#limit(int, Duration) Sinks.many().replay().limit(size, maxAge)}
* (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5.
*/
@Deprecated
public static ReplayProcessor createSizeAndTimeout(int size, Duration maxAge) {
return createSizeAndTimeout(size, maxAge, Schedulers.parallel());
}
/**
* Creates a time- and size-bounded replay processor.
*
* In this setting, the {@code ReplayProcessor} internally tags each received item
* with a timestamp value supplied by the {@link Scheduler} and holds at most
* {@code size} items in its internal buffer. It evicts items from the start of the
* buffer if their age becomes less-than or equal to the supplied age in milliseconds
* or the buffer reaches its {@code size} limit.
*
* When subscribers subscribe to a terminated {@code ReplayProcessor}, they observe
* the items that remained in the buffer after the terminal signal, regardless of
* their age, but at most {@code size} items.
*
* If an subscriber subscribes while the {@code ReplayProcessor} is active, it will
* observe only those items from within the buffer that have age less than the
* specified time and each subsequent item, even if the buffer evicts items due to the
* time constraint in the mean time. In other words, once an subscriber subscribes, it
* observes items without gaps in the sequence except for the outdated items at the
* beginning of the sequence.
*
*
* @param the type of items observed and emitted by the Processor
* @param maxAge the maximum age of the contained items in milliseconds
* @param size the maximum number of buffered items
* @param scheduler the {@link Scheduler} that provides the current time
*
* @return a new {@link ReplayProcessor} that replay up to {@code size} elements, but
* will evict them from its history based on their age.
* @deprecated use {@link Sinks.MulticastReplaySpec#limit(int, Duration, Scheduler) Sinks.many().replay().limit(size, maxAge, scheduler)}
* (or the unsafe variant if you're sure about external synchronization). To be removed in 3.5.
*/
@Deprecated
public static ReplayProcessor createSizeAndTimeout(int size,
Duration maxAge,
Scheduler scheduler) {
Objects.requireNonNull(scheduler, "scheduler is null");
if (size <= 0) {
throw new IllegalArgumentException("size > 0 required but it was " + size);
}
return new ReplayProcessor<>(new FluxReplay.SizeAndTimeBoundReplayBuffer<>(size,
maxAge.toNanos(),
scheduler));
}
final FluxReplay.ReplayBuffer buffer;
Subscription subscription;
volatile FluxReplay.ReplaySubscription[] subscribers;
@SuppressWarnings("rawtypes")
static final AtomicReferenceFieldUpdater
SUBSCRIBERS = AtomicReferenceFieldUpdater.newUpdater(ReplayProcessor.class,
FluxReplay.ReplaySubscription[].class,
"subscribers");
ReplayProcessor(FluxReplay.ReplayBuffer buffer) {
this.buffer = buffer;
SUBSCRIBERS.lazySet(this, EMPTY);
}
@Override
public void subscribe(CoreSubscriber actual) {
Objects.requireNonNull(actual, "subscribe");
FluxReplay.ReplaySubscription rs = new ReplayInner<>(actual, this);
actual.onSubscribe(rs);
if (add(rs)) {
if (rs.isCancelled()) {
remove(rs);
return;
}
}
buffer.replay(rs);
}
@Override
@Nullable
public Throwable getError() {
return buffer.getError();
}
@Override
@Nullable
public Object scanUnsafe(Attr key) {
if (key == Attr.PARENT){
return subscription;
}
if (key == Attr.CAPACITY) return buffer.capacity();
return super.scanUnsafe(key);
}
@Override
public Stream inners() {
return Stream.of(subscribers);
}
@Override
public long downstreamCount() {
return subscribers.length;
}
@Override
public boolean isTerminated() {
return buffer.isDone();
}
boolean add(FluxReplay.ReplaySubscription rs) {
for (; ; ) {
FluxReplay.ReplaySubscription[] a = subscribers;
if (a == TERMINATED) {
return false;
}
int n = a.length;
@SuppressWarnings("unchecked") FluxReplay.ReplaySubscription[] b =
new ReplayInner[n + 1];
System.arraycopy(a, 0, b, 0, n);
b[n] = rs;
if (SUBSCRIBERS.compareAndSet(this, a, b)) {
return true;
}
}
}
@SuppressWarnings("unchecked")
void remove(FluxReplay.ReplaySubscription rs) {
outer:
for (; ; ) {
FluxReplay.ReplaySubscription[] a = subscribers;
if (a == TERMINATED || a == EMPTY) {
return;
}
int n = a.length;
for (int i = 0; i < n; i++) {
if (a[i] == rs) {
FluxReplay.ReplaySubscription[] b;
if (n == 1) {
b = EMPTY;
}
else {
b = new ReplayInner[n - 1];
System.arraycopy(a, 0, b, 0, i);
System.arraycopy(a, i + 1, b, i, n - i - 1);
}
if (SUBSCRIBERS.compareAndSet(this, a, b)) {
return;
}
continue outer;
}
}
break;
}
}
@Override
public void onSubscribe(Subscription s) {
if (buffer.isDone()) {
s.cancel();
}
else if (Operators.validate(subscription, s)) {
subscription = s;
s.request(Long.MAX_VALUE);
}
}
@Override
public Context currentContext() {
return Operators.multiSubscribersContext(subscribers);
}
@Override
public int getPrefetch() {
return Integer.MAX_VALUE;
}
@Override
public void onComplete() {
//no particular error condition handling for onComplete
@SuppressWarnings("unused") Sinks.EmitResult emitResult = tryEmitComplete();
}
@Override
public Sinks.EmitResult tryEmitComplete() {
FluxReplay.ReplayBuffer b = buffer;
if (b.isDone()) {
return Sinks.EmitResult.FAIL_TERMINATED;
}
b.onComplete();
@SuppressWarnings("unchecked") FluxReplay.ReplaySubscription[] a =
SUBSCRIBERS.getAndSet(this, TERMINATED);
for (FluxReplay.ReplaySubscription rs : a) {
b.replay(rs);
}
return EmitResult.OK;
}
@Override
public void onError(Throwable throwable) {
emitError(throwable, Sinks.EmitFailureHandler.FAIL_FAST);
}
@Override
public EmitResult tryEmitError(Throwable t) {
FluxReplay.ReplayBuffer b = buffer;
if (b.isDone()) {
return EmitResult.FAIL_TERMINATED;
}
b.onError(t);
@SuppressWarnings("unchecked") FluxReplay.ReplaySubscription[] a =
SUBSCRIBERS.getAndSet(this, TERMINATED);
for (FluxReplay.ReplaySubscription rs : a) {
b.replay(rs);
}
return EmitResult.OK;
}
@Override
public void onNext(T t) {
emitNext(t, Sinks.EmitFailureHandler.FAIL_FAST);
}
@Override
public Sinks.EmitResult tryEmitNext(T t) {
FluxReplay.ReplayBuffer b = buffer;
if (b.isDone()) {
return Sinks.EmitResult.FAIL_TERMINATED;
}
//note: ReplayProcessor can so far ALWAYS buffer the element, no FAIL_ZERO_SUBSCRIBER here
b.add(t);
for (FluxReplay.ReplaySubscription rs : subscribers) {
b.replay(rs);
}
return Sinks.EmitResult.OK;
}
@Override
public int currentSubscriberCount() {
return subscribers.length;
}
@Override
public Flux asFlux() {
return this;
}
@Override
protected boolean isIdentityProcessor() {
return true;
}
static final class ReplayInner
implements FluxReplay.ReplaySubscription {
final CoreSubscriber actual;
final ReplayProcessor parent;
final FluxReplay.ReplayBuffer buffer;
int index;
int tailIndex;
Object node;
volatile int wip;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater WIP =
AtomicIntegerFieldUpdater.newUpdater(ReplayInner.class,
"wip");
volatile long requested;
@SuppressWarnings("rawtypes")
static final AtomicLongFieldUpdater REQUESTED =
AtomicLongFieldUpdater.newUpdater(ReplayInner.class,
"requested");
int fusionMode;
ReplayInner(CoreSubscriber actual,
ReplayProcessor parent) {
this.actual = actual;
this.parent = parent;
this.buffer = parent.buffer;
}
@Override
public long requested() {
return requested;
}
@Override
public boolean isCancelled() {
return requested == Long.MIN_VALUE;
}
@Override
public CoreSubscriber actual() {
return actual;
}
@Override
public int requestFusion(int requestedMode) {
if ((requestedMode & ASYNC) != 0) {
fusionMode = ASYNC;
return ASYNC;
}
return NONE;
}
@Override
@Nullable
public T poll() {
return buffer.poll(this);
}
@Override
public void clear() {
buffer.clear(this);
}
@Override
public boolean isEmpty() {
return buffer.isEmpty(this);
}
@Override
public int size() {
return buffer.size(this);
}
@Override
public void request(long n) {
if (Operators.validate(n)) {
if (fusionMode() == NONE) {
Operators.addCapCancellable(REQUESTED, this, n);
}
buffer.replay(this);
}
}
@Override
public void requestMore(int index) {
this.index = index;
}
@Override
public void cancel() {
if (REQUESTED.getAndSet(this, Long.MIN_VALUE) != Long.MIN_VALUE) {
parent.remove(this);
if (enter()) {
node = null;
}
}
}
@Override
public void node(@Nullable Object node) {
this.node = node;
}
@Override
public int fusionMode() {
return fusionMode;
}
@Override
@Nullable
public Object node() {
return node;
}
@Override
public int index() {
return index;
}
@Override
public void index(int index) {
this.index = index;
}
@Override
public int tailIndex() {
return tailIndex;
}
@Override
public void tailIndex(int tailIndex) {
this.tailIndex = tailIndex;
}
@Override
public boolean enter() {
return WIP.getAndIncrement(this) == 0;
}
@Override
public int leave(int missed) {
return WIP.addAndGet(this, -missed);
}
@Override
public void produced(long n) {
REQUESTED.addAndGet(this, -n);
}
}
}