reactor.core.publisher.FluxReplay Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redisson-all Show documentation
Show all versions of redisson-all Show documentation
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
/*
* 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.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
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.stream.Stream;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.CorePublisher;
import reactor.core.CoreSubscriber;
import reactor.core.Disposable;
import reactor.core.Fuseable;
import reactor.core.Scannable;
import reactor.core.scheduler.Scheduler;
import reactor.util.annotation.Nullable;
import reactor.util.concurrent.Queues;
import reactor.util.context.Context;
/**
* @param
* @see Reactive-Streams-Commons
*/
final class FluxReplay extends ConnectableFlux
implements Scannable, Fuseable, OptimizableOperator {
final CorePublisher source;
final int history;
final long ttl;
final Scheduler scheduler;
volatile ReplaySubscriber connection;
@SuppressWarnings("rawtypes")
static final AtomicReferenceFieldUpdater CONNECTION =
AtomicReferenceFieldUpdater.newUpdater(FluxReplay.class,
ReplaySubscriber.class,
"connection");
@Nullable
final OptimizableOperator, T> optimizableOperator;
interface ReplaySubscription extends QueueSubscription, InnerProducer {
@Override
CoreSubscriber super T> actual();
boolean enter();
int leave(int missed);
void produced(long n);
void node(@Nullable Object node);
@Nullable
Object node();
int tailIndex();
void tailIndex(int tailIndex);
int index();
void index(int index);
int fusionMode();
boolean isCancelled();
long requested();
void requestMore(int index);
}
interface ReplayBuffer {
void add(T value);
void onError(Throwable ex);
@Nullable
Throwable getError();
void onComplete();
void replay(ReplaySubscription rs);
boolean isDone();
@Nullable
T poll(ReplaySubscription rs);
void clear(ReplaySubscription rs);
boolean isEmpty(ReplaySubscription rs);
int size(ReplaySubscription rs);
int size();
int capacity();
boolean isExpired();
}
static final class SizeAndTimeBoundReplayBuffer implements ReplayBuffer {
static final class TimedNode extends AtomicReference> {
final int index;
final T value;
final long time;
TimedNode(int index, @Nullable T value, long time) {
this.index = index;
this.value = value;
this.time = time;
}
@Override
public String toString() {
return "TimedNode{" +
"index=" + index +
", value=" + value +
", time=" + time +
'}';
}
}
final int limit;
final int indexUpdateLimit;
final long maxAge;
final Scheduler scheduler;
int size;
volatile TimedNode head;
TimedNode tail;
Throwable error;
static final long NOT_DONE = Long.MIN_VALUE;
volatile long done = NOT_DONE;
SizeAndTimeBoundReplayBuffer(int limit,
long maxAge,
Scheduler scheduler) {
this.limit = limit;
this.indexUpdateLimit = Operators.unboundedOrLimit(limit);
this.maxAge = maxAge;
this.scheduler = scheduler;
TimedNode h = new TimedNode<>(-1, null, 0L);
this.tail = h;
this.head = h;
}
@Override
public boolean isExpired() {
long done = this.done;
return done != NOT_DONE && scheduler.now(TimeUnit.NANOSECONDS) - maxAge > done;
}
@SuppressWarnings("unchecked")
void replayNormal(ReplaySubscription rs) {
int missed = 1;
final Subscriber super T> a = rs.actual();
for (; ; ) {
@SuppressWarnings("unchecked") TimedNode node =
(TimedNode) rs.node();
if (node == null) {
node = head;
if (done == NOT_DONE) {
// skip old entries
long limit = scheduler.now(TimeUnit.NANOSECONDS) - maxAge;
TimedNode next = node;
while (next != null) {
long ts = next.time;
if (ts > limit) {
break;
}
node = next;
next = node.get();
}
}
}
long r = rs.requested();
long e = 0L;
while (e != r) {
if (rs.isCancelled()) {
rs.node(null);
return;
}
boolean d = done != NOT_DONE;
TimedNode next = node.get();
boolean empty = next == null;
if (d && empty) {
rs.node(null);
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
if (empty) {
break;
}
a.onNext(next.value);
e++;
node = next;
if ((next.index + 1) % indexUpdateLimit == 0) {
rs.requestMore(next.index + 1);
}
}
if (e == r) {
if (rs.isCancelled()) {
rs.node(null);
return;
}
boolean d = done != NOT_DONE;
boolean empty = node.get() == null;
if (d && empty) {
rs.node(null);
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
}
if (e != 0L) {
if (r != Long.MAX_VALUE) {
rs.produced(e);
}
}
rs.node(node);
missed = rs.leave(missed);
if (missed == 0) {
break;
}
}
}
void replayFused(ReplaySubscription rs) {
int missed = 1;
final Subscriber super T> a = rs.actual();
for (; ; ) {
if (rs.isCancelled()) {
rs.node(null);
return;
}
boolean d = done != NOT_DONE;
a.onNext(null);
if (d) {
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
missed = rs.leave(missed);
if (missed == 0) {
break;
}
}
}
@Override
public void onError(Throwable ex) {
done = scheduler.now(TimeUnit.NANOSECONDS);
error = ex;
}
@Override
@Nullable
public Throwable getError() {
return error;
}
@Override
public void onComplete() {
done = scheduler.now(TimeUnit.NANOSECONDS);
}
@Override
public boolean isDone() {
return done != NOT_DONE;
}
@SuppressWarnings("unchecked")
TimedNode latestHead(ReplaySubscription rs) {
long now = scheduler.now(TimeUnit.NANOSECONDS) - maxAge;
TimedNode h = (TimedNode) rs.node();
if (h == null) {
h = head;
}
TimedNode n;
while ((n = h.get()) != null) {
if (n.time > now) {
break;
}
h = n;
}
return h;
}
@Override
@Nullable
public T poll(ReplaySubscription rs) {
TimedNode node = latestHead(rs);
TimedNode next;
long now = scheduler.now(TimeUnit.NANOSECONDS) - maxAge;
while ((next = node.get()) != null) {
if (next.time > now) {
node = next;
break;
}
node = next;
}
if (next == null) {
if (node.index != -1 && (node.index + 1) % indexUpdateLimit == 0) {
rs.requestMore(node.index + 1);
}
return null;
}
rs.node(next);
if ((next.index + 1) % indexUpdateLimit == 0) {
rs.requestMore(next.index + 1);
}
return node.value;
}
@Override
public void clear(ReplaySubscription rs) {
rs.node(null);
}
@Override
@SuppressWarnings("unchecked")
public boolean isEmpty(ReplaySubscription rs) {
TimedNode node = latestHead(rs);
return node.get() == null;
}
@Override
public int size(ReplaySubscription rs) {
TimedNode node = latestHead(rs);
int count = 0;
TimedNode next;
while ((next = node.get()) != null && count != Integer.MAX_VALUE) {
count++;
node = next;
}
return count;
}
@Override
public int size() {
TimedNode node = head;
int count = 0;
TimedNode next;
while ((next = node.get()) != null && count != Integer.MAX_VALUE) {
count++;
node = next;
}
return count;
}
@Override
public int capacity() {
return limit;
}
@Override
public void add(T value) {
final TimedNode tail = this.tail;
final TimedNode valueNode = new TimedNode<>(tail.index + 1,
value,
scheduler.now(TimeUnit.NANOSECONDS));
tail.set(valueNode);
this.tail = valueNode;
int s = size;
if (s == limit) {
head = head.get();
}
else {
size = s + 1;
}
long limit = scheduler.now(TimeUnit.NANOSECONDS) - maxAge;
//short-circuit if maxAge == 0: no sense in keeping any old value.
//we still want to keep the newly added value in order for the immediately following replay
//to propagate it to currently registered subscribers.
if (maxAge == 0) {
head = valueNode;
return;
}
//otherwise, start walking the linked list in order to find the first node > time limit
//(in which case we'll have passed all nodes that have reached the TTL).
TimedNode h = head;
TimedNode next;
int removed = 0;
for (; ; ) {
next = h.get();
if (next == null) {
break;
}
if (next.time > limit || next == valueNode) {
//next == valueNode case causes head swap and actual removal, even if its time cannot be > limit.
//otherwise we'd skip removal and re-walk the whole linked list on next add, retaining outdated values for nothing.
if (removed != 0) {
size = size - removed;
head = h;
}
break;
}
h = next;
removed++;
}
}
@Override
@SuppressWarnings("unchecked")
public void replay(ReplaySubscription rs) {
if (!rs.enter()) {
return;
}
if (rs.fusionMode() == NONE) {
replayNormal(rs);
}
else {
replayFused(rs);
}
}
}
static final class UnboundedReplayBuffer implements ReplayBuffer {
final int batchSize;
final int indexUpdateLimit;
volatile int size;
final Object[] head;
Object[] tail;
int tailIndex;
volatile boolean done;
Throwable error;
UnboundedReplayBuffer(int batchSize) {
this.batchSize = batchSize;
this.indexUpdateLimit = Operators.unboundedOrLimit(batchSize);
Object[] n = new Object[batchSize + 1];
this.tail = n;
this.head = n;
}
@Override
public boolean isExpired() {
return false;
}
@Override
@Nullable
public Throwable getError() {
return error;
}
@Override
public int capacity() {
return Integer.MAX_VALUE;
}
@Override
public void add(T value) {
int i = tailIndex;
Object[] a = tail;
if (i == a.length - 1) {
Object[] b = new Object[a.length];
b[0] = value;
tailIndex = 1;
a[i] = b;
tail = b;
}
else {
a[i] = value;
tailIndex = i + 1;
}
size++;
}
@Override
public void onError(Throwable ex) {
error = ex;
done = true;
}
@Override
public void onComplete() {
done = true;
}
void replayNormal(ReplaySubscription rs) {
int missed = 1;
final Subscriber super T> a = rs.actual();
final int n = batchSize;
for (; ; ) {
long r = rs.requested();
long e = 0L;
Object[] node = (Object[]) rs.node();
if (node == null) {
node = head;
}
int tailIndex = rs.tailIndex();
int index = rs.index();
while (e != r) {
if (rs.isCancelled()) {
rs.node(null);
return;
}
boolean d = done;
boolean empty = index == size;
if (d && empty) {
rs.node(null);
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
if (empty) {
break;
}
if (tailIndex == n) {
node = (Object[]) node[tailIndex];
tailIndex = 0;
}
@SuppressWarnings("unchecked") T v = (T) node[tailIndex];
a.onNext(v);
e++;
tailIndex++;
index++;
if (index % indexUpdateLimit == 0) {
rs.requestMore(index);
}
}
if (e == r) {
if (rs.isCancelled()) {
rs.node(null);
return;
}
boolean d = done;
boolean empty = index == size;
if (d && empty) {
rs.node(null);
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
}
if (e != 0L) {
if (r != Long.MAX_VALUE) {
rs.produced(e);
}
}
rs.index(index);
rs.tailIndex(tailIndex);
rs.node(node);
missed = rs.leave(missed);
if (missed == 0) {
break;
}
}
}
void replayFused(ReplaySubscription rs) {
int missed = 1;
final Subscriber super T> a = rs.actual();
for (; ; ) {
if (rs.isCancelled()) {
rs.node(null);
return;
}
boolean d = done;
a.onNext(null);
if (d) {
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
missed = rs.leave(missed);
if (missed == 0) {
break;
}
}
}
@Override
public void replay(ReplaySubscription rs) {
if (!rs.enter()) {
return;
}
if (rs.fusionMode() == NONE) {
replayNormal(rs);
}
else {
replayFused(rs);
}
}
@Override
public boolean isDone() {
return done;
}
@Override
@Nullable
public T poll(ReplaySubscription rs) {
int index = rs.index();
if (index == size) {
return null;
}
Object[] node = (Object[]) rs.node();
if (node == null) {
node = head;
rs.node(node);
}
int tailIndex = rs.tailIndex();
if (tailIndex == batchSize) {
node = (Object[]) node[tailIndex];
tailIndex = 0;
rs.node(node);
}
@SuppressWarnings("unchecked") T v = (T) node[tailIndex];
rs.tailIndex(tailIndex + 1);
if ((index + 1) % indexUpdateLimit == 0) {
rs.requestMore(index + 1);
}
else {
rs.index(index + 1);
}
return v;
}
@Override
public void clear(ReplaySubscription rs) {
rs.node(null);
}
@Override
public boolean isEmpty(ReplaySubscription rs) {
return rs.index() == size;
}
@Override
public int size(ReplaySubscription rs) {
return size - rs.index();
}
@Override
public int size() {
return size;
}
}
static final class SizeBoundReplayBuffer implements ReplayBuffer {
final int limit;
final int indexUpdateLimit;
volatile Node head;
Node tail;
int size;
volatile boolean done;
Throwable error;
SizeBoundReplayBuffer(int limit) {
if (limit < 0) {
throw new IllegalArgumentException("Limit cannot be negative");
}
this.limit = limit;
this.indexUpdateLimit = Operators.unboundedOrLimit(limit);
Node n = new Node<>(-1, null);
this.tail = n;
this.head = n;
}
@Override
public boolean isExpired() {
return false;
}
@Override
public int capacity() {
return limit;
}
@Override
public void add(T value) {
final Node tail = this.tail;
final Node n = new Node<>(tail.index + 1, value);
tail.set(n);
this.tail = n;
int s = size;
if (s == limit) {
head = head.get();
}
else {
size = s + 1;
}
}
@Override
public void onError(Throwable ex) {
error = ex;
done = true;
}
@Override
public void onComplete() {
done = true;
}
void replayNormal(ReplaySubscription rs) {
final Subscriber super T> a = rs.actual();
int missed = 1;
for (; ; ) {
long r = rs.requested();
long e = 0L;
@SuppressWarnings("unchecked") Node node = (Node) rs.node();
if (node == null) {
node = head;
}
while (e != r) {
if (rs.isCancelled()) {
rs.node(null);
return;
}
boolean d = done;
Node next = node.get();
boolean empty = next == null;
if (d && empty) {
rs.node(null);
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
if (empty) {
break;
}
a.onNext(next.value);
e++;
node = next;
if ((next.index + 1) % indexUpdateLimit == 0) {
rs.requestMore(next.index + 1);
}
}
if (e == r) {
if (rs.isCancelled()) {
rs.node(null);
return;
}
boolean d = done;
boolean empty = node.get() == null;
if (d && empty) {
rs.node(null);
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
}
if (e != 0L) {
if (r != Long.MAX_VALUE) {
rs.produced(e);
}
}
rs.node(node);
missed = rs.leave(missed);
if (missed == 0) {
break;
}
}
}
void replayFused(ReplaySubscription rs) {
int missed = 1;
final Subscriber super T> a = rs.actual();
for (; ; ) {
if (rs.isCancelled()) {
rs.node(null);
return;
}
boolean d = done;
a.onNext(null);
if (d) {
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
missed = rs.leave(missed);
if (missed == 0) {
break;
}
}
}
@Override
public void replay(ReplaySubscription rs) {
if (!rs.enter()) {
return;
}
if (rs.fusionMode() == NONE) {
replayNormal(rs);
}
else {
replayFused(rs);
}
}
@Override
@Nullable
public Throwable getError() {
return error;
}
@Override
public boolean isDone() {
return done;
}
static final class Node extends AtomicReference> {
/** */
private static final long serialVersionUID = 3713592843205853725L;
final int index;
final T value;
Node(int index, @Nullable T value) {
this.index = index;
this.value = value;
}
@Override
public String toString() {
return "Node(" + value + ")";
}
}
@Override
@Nullable
public T poll(ReplaySubscription rs) {
@SuppressWarnings("unchecked") Node node = (Node) rs.node();
if (node == null) {
node = head;
rs.node(node);
}
Node next = node.get();
if (next == null) {
return null;
}
rs.node(next);
if ((next.index + 1) % indexUpdateLimit == 0) {
rs.requestMore(next.index + 1);
}
return next.value;
}
@Override
public void clear(ReplaySubscription rs) {
rs.node(null);
}
@Override
public boolean isEmpty(ReplaySubscription rs) {
@SuppressWarnings("unchecked") Node node = (Node) rs.node();
if (node == null) {
node = head;
rs.node(node);
}
return node.get() == null;
}
@Override
public int size(ReplaySubscription rs) {
@SuppressWarnings("unchecked") Node node = (Node) rs.node();
if (node == null) {
node = head;
}
int count = 0;
Node next;
while ((next = node.get()) != null && count != Integer.MAX_VALUE) {
count++;
node = next;
}
return count;
}
@Override
public int size() {
Node node = head;
int count = 0;
Node next;
while ((next = node.get()) != null && count != Integer.MAX_VALUE) {
count++;
node = next;
}
return count;
}
}
FluxReplay(CorePublisher source,
int history,
long ttl,
@Nullable Scheduler scheduler) {
this.source = Objects.requireNonNull(source, "source");
if (source instanceof OptimizableOperator) {
@SuppressWarnings("unchecked")
OptimizableOperator, T> optimSource = (OptimizableOperator, T>) source;
this.optimizableOperator = optimSource;
}
else {
this.optimizableOperator = null;
}
if (history <= 0) {
throw new IllegalArgumentException("History cannot be zero or negative : " + history);
}
this.history = history;
if (scheduler != null && ttl < 0) {
throw new IllegalArgumentException("TTL cannot be negative : " + ttl);
}
this.ttl = ttl;
this.scheduler = scheduler;
}
@Override
public int getPrefetch() {
return history;
}
ReplaySubscriber newState() {
if (scheduler != null) {
return new ReplaySubscriber<>(new SizeAndTimeBoundReplayBuffer<>(history,
ttl,
scheduler), this, history);
}
if (history != Integer.MAX_VALUE) {
return new ReplaySubscriber<>(new SizeBoundReplayBuffer<>(history),
this,
history);
}
return new ReplaySubscriber<>(new UnboundedReplayBuffer<>(Queues.SMALL_BUFFER_SIZE),
this,
Queues.SMALL_BUFFER_SIZE);
}
@Override
public void connect(Consumer super Disposable> cancelSupport) {
boolean doConnect;
ReplaySubscriber s;
for (; ; ) {
s = connection;
if (s == null) {
ReplaySubscriber u = newState();
if (!CONNECTION.compareAndSet(this, null, u)) {
continue;
}
s = u;
}
doConnect = s.tryConnect();
break;
}
cancelSupport.accept(s);
if (doConnect) {
try {
source.subscribe(s);
}
catch (Throwable e) {
Operators.reportThrowInSubscribe(s, e);
}
}
}
@Override
@SuppressWarnings("unchecked")
public void subscribe(CoreSubscriber super T> actual) {
try {
CoreSubscriber nextSubscriber = subscribeOrReturn(actual);
if (nextSubscriber == null) {
return;
}
source.subscribe(nextSubscriber);
}
catch (Throwable e) {
Operators.error(actual, Operators.onOperatorError(e, actual.currentContext()));
}
}
@Override
public final CoreSubscriber super T> subscribeOrReturn(CoreSubscriber super T> actual)
throws Throwable {
boolean expired;
for (; ; ) {
ReplaySubscriber c = connection;
expired = scheduler != null && c != null && c.buffer.isExpired();
if (c == null || expired) {
ReplaySubscriber u = newState();
if (!CONNECTION.compareAndSet(this, c, u)) {
continue;
}
c = u;
}
ReplayInner inner = new ReplayInner<>(actual, c);
actual.onSubscribe(inner);
c.add(inner);
if (inner.isCancelled()) {
c.remove(inner);
return null;
}
c.buffer.replay(inner);
if (expired) {
return c;
}
break;
}
return null;
}
@Override
public final CorePublisher extends T> source() {
return source;
}
@Override
public final OptimizableOperator, ? extends T> nextOptimizableSource() {
return optimizableOperator;
}
@Override
@Nullable
public Object scanUnsafe(Scannable.Attr key) {
if (key == Attr.PREFETCH) return getPrefetch();
if (key == Attr.PARENT) return source;
if (key == Attr.RUN_ON) return scheduler;
if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC;
if (key == InternalProducerAttr.INSTANCE) return true;
return null;
}
static final class ReplaySubscriber implements InnerConsumer, Disposable {
final FluxReplay parent;
final ReplayBuffer buffer;
final long prefetch;
final int limit;
Subscription s;
int produced;
int nextPrefetchIndex;
volatile ReplaySubscription[] subscribers;
volatile long state;
@SuppressWarnings("rawtypes")
static final AtomicLongFieldUpdater STATE =
AtomicLongFieldUpdater.newUpdater(ReplaySubscriber.class, "state");
@SuppressWarnings("rawtypes")
static final ReplaySubscription[] EMPTY = new ReplaySubscription[0];
@SuppressWarnings("rawtypes")
static final ReplaySubscription[] TERMINATED = new ReplaySubscription[0];
@SuppressWarnings("unchecked")
ReplaySubscriber(ReplayBuffer buffer, FluxReplay parent, int prefetch) {
this.buffer = buffer;
this.parent = parent;
this.subscribers = EMPTY;
this.prefetch = Operators.unboundedOrPrefetch(prefetch);
this.limit = Operators.unboundedOrLimit(prefetch);
this.nextPrefetchIndex = this.limit;
}
@Override
public void onSubscribe(Subscription s) {
if (buffer.isDone()) {
s.cancel();
return;
}
if (Operators.validate(this.s, s)) {
this.s = s;
final long previousState = markSubscribed(this);
if (isDisposed(previousState)) {
s.cancel();
return;
}
s.request(this.prefetch);
}
}
void manageRequest(long currentState) {
final Subscription p = this.s;
for (; ; ) {
int nextPrefetchIndex = this.nextPrefetchIndex;
boolean shouldPrefetch;
// find out if we need to make another prefetch
final ReplaySubscription[] subscribers = this.subscribers;
if (subscribers.length > 0) {
shouldPrefetch = true;
for (ReplaySubscription rp : subscribers) {
if (rp.index() < nextPrefetchIndex) {
shouldPrefetch = false;
break;
}
}
}
else {
shouldPrefetch = this.produced >= nextPrefetchIndex;
}
if (shouldPrefetch) {
final int limit = this.limit;
this.nextPrefetchIndex = nextPrefetchIndex + limit;
p.request(limit);
}
currentState = markWorkDone(this, currentState);
// if the upstream has completed, no more requesting is possible
if (isDisposed(currentState)) {
return;
}
if (!isWorkInProgress(currentState)) {
return;
}
}
}
@Override
public void onNext(T t) {
ReplayBuffer b = buffer;
if (b.isDone()) {
Operators.onNextDropped(t, currentContext());
return;
}
produced++;
b.add(t);
final ReplaySubscription[] subscribers = this.subscribers;
if (subscribers.length == 0) {
if (produced % limit == 0) {
final long previousState = markWorkAdded(this);
if (isDisposed(previousState)) {
return;
}
if (isWorkInProgress(previousState)) {
return;
}
manageRequest(previousState + 1);
}
return;
}
for (ReplaySubscription rs : subscribers) {
b.replay(rs);
}
}
@Override
public void onError(Throwable t) {
ReplayBuffer b = buffer;
if (b.isDone()) {
Operators.onErrorDropped(t, currentContext());
}
else {
b.onError(t);
for (ReplaySubscription rs : terminate()) {
b.replay(rs);
}
}
}
@Override
public void onComplete() {
ReplayBuffer b = buffer;
if (!b.isDone()) {
b.onComplete();
for (ReplaySubscription rs : terminate()) {
b.replay(rs);
}
}
}
@Override
public void dispose() {
final long previousState = markDisposed(this);
if (isDisposed(previousState)) {
return;
}
if (isSubscribed(previousState)) {
s.cancel();
}
CONNECTION.lazySet(parent, null);
final CancellationException ex = new CancellationException("Disconnected");
final ReplayBuffer buffer = this.buffer;
buffer.onError(ex);
for (ReplaySubscription inner : terminate()) {
buffer.replay(inner);
}
}
boolean add(ReplayInner inner) {
if (subscribers == TERMINATED) {
return false;
}
synchronized (this) {
ReplaySubscription[] a = subscribers;
if (a == TERMINATED) {
return false;
}
int n = a.length;
@SuppressWarnings("unchecked") ReplayInner[] b =
new ReplayInner[n + 1];
System.arraycopy(a, 0, b, 0, n);
b[n] = inner;
subscribers = b;
return true;
}
}
@SuppressWarnings("unchecked")
void remove(ReplaySubscription inner) {
ReplaySubscription[] a = subscribers;
if (a == TERMINATED || a == EMPTY) {
return;
}
synchronized (this) {
a = subscribers;
if (a == TERMINATED || a == EMPTY) {
return;
}
int j = -1;
int n = a.length;
for (int i = 0; i < n; i++) {
if (a[i] == inner) {
j = i;
break;
}
}
if (j < 0) {
return;
}
ReplaySubscription[] b;
if (n == 1) {
b = EMPTY;
}
else {
b = new ReplayInner[n - 1];
System.arraycopy(a, 0, b, 0, j);
System.arraycopy(a, j + 1, b, j, n - j - 1);
}
subscribers = b;
}
}
@SuppressWarnings("unchecked")
ReplaySubscription[] terminate() {
ReplaySubscription[] a = subscribers;
if (a == TERMINATED) {
return a;
}
synchronized (this) {
a = subscribers;
if (a != TERMINATED) {
subscribers = TERMINATED;
}
return a;
}
}
boolean isTerminated() {
return subscribers == TERMINATED;
}
boolean tryConnect() {
return markConnected(this);
}
@Override
public Context currentContext() {
return Operators.multiSubscribersContext(subscribers);
}
@Override
@Nullable
public Object scanUnsafe(Attr key) {
if (key == Attr.PARENT) return s;
if (key == Attr.PREFETCH) return Integer.MAX_VALUE;
if (key == Attr.CAPACITY) return buffer.capacity();
if (key == Attr.ERROR) return buffer.getError();
if (key == Attr.BUFFERED) return buffer.size();
if (key == Attr.TERMINATED) return isTerminated();
if (key == Attr.CANCELLED) return isDisposed();
if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC;
return null;
}
@Override
public Stream extends Scannable> inners() {
return Stream.of(subscribers);
}
@Override
public boolean isDisposed() {
return isDisposed(this.state);
}
static final long CONNECTED_FLAG =
0b0001_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L;
static final long SUBSCRIBED_FLAG =
0b0010_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L;
static final long DISPOSED_FLAG =
0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L;
static final long WORK_IN_PROGRESS_MAX_VALUE =
0b0000_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111L;
/**
* Adds {@link #CONNECTED_FLAG} to the state. Fails if the flag is already set
*
* @param instance to operate on
* @return true if flag was set
*/
static boolean markConnected(ReplaySubscriber> instance) {
for (; ; ) {
final long state = instance.state;
if (isConnected(state)) {
return false;
}
if (STATE.compareAndSet(instance, state, state | CONNECTED_FLAG)) {
return true;
}
}
}
/**
* Adds {@link #SUBSCRIBED_FLAG} to the state. Fails if states has the flag {@link
* #DISPOSED_FLAG}
*
* @param instance to operate on
* @return previous observed state
*/
static long markSubscribed(ReplaySubscriber> instance) {
for (; ; ) {
final long state = instance.state;
if (isDisposed(state)) {
return state;
}
if (STATE.compareAndSet(instance, state, state | SUBSCRIBED_FLAG)) {
return state;
}
}
}
/**
* Increments the work in progress part of the state, up to its max value. Fails
* if states has already had the {@link #DISPOSED_FLAG} flag
*
* @param instance to operate on
* @return previous observed state
*/
static long markWorkAdded(ReplaySubscriber> instance) {
for (; ; ) {
final long state = instance.state;
if (isDisposed(state)) {
return state;
}
if ((state & WORK_IN_PROGRESS_MAX_VALUE) == WORK_IN_PROGRESS_MAX_VALUE) {
return state;
}
if (STATE.compareAndSet(instance, state, state + 1)) {
return state;
}
}
}
/**
* Sets work in progress to zero. Fails if given states not equal to the actual
* state.
*
* @param instance to operate on
* @return previous observed state
*/
static long markWorkDone(ReplaySubscriber> instance, long currentState) {
for (; ; ) {
final long state = instance.state;
if (currentState != state) {
return state;
}
final long nextState = state & ~WORK_IN_PROGRESS_MAX_VALUE;
if (STATE.compareAndSet(instance, state, nextState)) {
return nextState;
}
}
}
/**
* Adds {@link #DISPOSED_FLAG} to the state. Fails if states has already had
* the flag
*
* @param instance to operate on
* @return previous observed state
*/
static long markDisposed(ReplaySubscriber> instance) {
for (; ; ) {
final long state = instance.state;
if (isDisposed(state)) {
return state;
}
if (STATE.compareAndSet(instance, state, state | DISPOSED_FLAG)) {
return state;
}
}
}
/**
* Check if state has {@link #CONNECTED_FLAG} flag indicating that the
* {@link #connect(Consumer)} method was called and we have already connected
* to the upstream
*
* @param state to check flag presence
* @return true if flag is set
*/
static boolean isConnected(long state) {
return (state & CONNECTED_FLAG) == CONNECTED_FLAG;
}
/**
* Check if state has {@link #SUBSCRIBED_FLAG} flag indicating subscription
* reception from the upstream
*
* @param state to check flag presence
* @return true if flag is set
*/
static boolean isSubscribed(long state) {
return (state & SUBSCRIBED_FLAG) == SUBSCRIBED_FLAG;
}
/**
* Check if states has bits indicating work in progress
*
* @param state to check there is any amount of work in progress
* @return true if there is work in progress
*/
static boolean isWorkInProgress(long state) {
return (state & WORK_IN_PROGRESS_MAX_VALUE) > 0;
}
/**
* Check if state has {@link #DISPOSED_FLAG} flag
*
* @param state to check flag presence
* @return true if flag is set
*/
static boolean isDisposed(long state) {
return (state & DISPOSED_FLAG) == DISPOSED_FLAG;
}
}
static final class ReplayInner implements ReplaySubscription {
final CoreSubscriber super T> actual;
final ReplaySubscriber parent;
int index;
int tailIndex;
Object node;
int fusionMode;
long totalRequested;
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");
ReplayInner(CoreSubscriber super T> actual, ReplaySubscriber parent) {
this.actual = actual;
this.parent = parent;
}
@Override
public void request(long n) {
if (Operators.validate(n)) {
if (Operators.addCapCancellable(REQUESTED, this, n) != Long.MIN_VALUE) {
// assuming no race between subscriptions#request
totalRequested = Operators.addCap(totalRequested, n);
parent.buffer.replay(this);
}
}
}
@Override
@Nullable
public Object scanUnsafe(Attr key) {
if (key == Attr.PARENT) {
return parent;
}
if (key == Attr.TERMINATED) {
return parent.isTerminated();
}
if (key == Attr.BUFFERED) {
return size();
}
if (key == Attr.CANCELLED) {
return isCancelled();
}
if (key == Attr.REQUESTED_FROM_DOWNSTREAM) {
return Math.max(0L, requested);
}
if (key == Attr.RUN_ON) {
return parent.parent.scheduler;
}
return ReplaySubscription.super.scanUnsafe(key);
}
@Override
public void cancel() {
if (REQUESTED.getAndSet(this, Long.MIN_VALUE) != Long.MIN_VALUE) {
parent.remove(this);
if (enter()) {
node = null;
}
}
}
@Override
public long requested() {
return requested;
}
@Override
public boolean isCancelled() {
return requested == Long.MIN_VALUE;
}
@Override
public CoreSubscriber super T> 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 parent.buffer.poll(this);
}
@Override
public void clear() {
parent.buffer.clear(this);
}
@Override
public boolean isEmpty() {
return parent.buffer.isEmpty(this);
}
@Override
public int size() {
return parent.buffer.size(this);
}
@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 void requestMore(int index) {
this.index = index;
final long previousState = ReplaySubscriber.markWorkAdded(this.parent);
if (ReplaySubscriber.isDisposed(previousState)) {
return;
}
if (ReplaySubscriber.isWorkInProgress(previousState)) {
return;
}
this.parent.manageRequest(previousState + 1);
}
@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);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy