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) 2011-2018 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
*
* 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;
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();
long signalConnectAndGetRequested();
int tailIndex();
void tailIndex(int tailIndex);
int index();
void index(int index);
int fusionMode();
boolean isCancelled();
long requested();
}
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 T value;
final long time;
TimedNode(@Nullable T value, long time) {
this.value = value;
this.time = time;
}
}
final int limit;
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.maxAge = maxAge;
this.scheduler = scheduler;
TimedNode h = new TimedNode<>(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 (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) {
return null;
}
rs.node(next);
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) {
TimedNode n = new TimedNode<>(value, scheduler.now(TimeUnit.NANOSECONDS));
tail.set(n);
tail = n;
int s = size;
if (s == limit) {
head = head.get();
}
else {
size = s + 1;
}
long limit = scheduler.now(TimeUnit.NANOSECONDS) - maxAge;
TimedNode h = head;
TimedNode next;
int removed = 0;
for (; ; ) {
next = h.get();
if (next == null) {
break;
}
if (next.time > limit) {
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;
volatile int size;
final Object[] head;
Object[] tail;
int tailIndex;
volatile boolean done;
Throwable error;
UnboundedReplayBuffer(int batchSize) {
this.batchSize = 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 (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.index(index + 1);
rs.tailIndex(tailIndex + 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;
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;
Node n = new Node<>(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) {
Node n = new Node<>(value);
tail.set(n);
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 (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 T value;
Node(@Nullable T value) {
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);
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;
}
}
@SuppressWarnings("rawtypes")
static final AtomicReferenceFieldUpdater CONNECTION =
AtomicReferenceFieldUpdater.newUpdater(FluxReplay.class,
ReplaySubscriber.class,
"connection");
@Nullable
final OptimizableOperator, T> optimizableOperator;
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;
}
this.history = history;
if(history < 0){
throw new IllegalArgumentException("History cannot be negative : " + 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);
}
if (history != Integer.MAX_VALUE) {
return new ReplaySubscriber<>(new SizeBoundReplayBuffer<>(history),
this);
}
return new ReplaySubscriber<>(new UnboundedReplayBuffer<>(Queues.SMALL_BUFFER_SIZE),
this);
}
@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(connection, e);
return;
}
}
}
@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()));
return;
}
}
@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, ReplaySubscriber.CONNECTED.get(c) == 0);
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;
return null;
}
static final class ReplaySubscriber
implements InnerConsumer, Disposable {
final FluxReplay parent;
final ReplayBuffer buffer;
volatile Subscription s;
@SuppressWarnings("rawtypes")
static final AtomicReferenceFieldUpdater S =
AtomicReferenceFieldUpdater.newUpdater(ReplaySubscriber.class,
Subscription.class,
"s");
volatile ReplaySubscription[] subscribers;
volatile int wip;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater WIP =
AtomicIntegerFieldUpdater.newUpdater(ReplaySubscriber.class, "wip");
volatile int connected;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater CONNECTED =
AtomicIntegerFieldUpdater.newUpdater(ReplaySubscriber.class, "connected");
@SuppressWarnings("rawtypes")
static final ReplaySubscription[] EMPTY = new ReplaySubscription[0];
@SuppressWarnings("rawtypes")
static final ReplaySubscription[] TERMINATED = new ReplaySubscription[0];
volatile boolean cancelled;
volatile boolean unbounded;
@SuppressWarnings("unchecked")
ReplaySubscriber(ReplayBuffer buffer,
FluxReplay parent) {
this.buffer = buffer;
this.parent = parent;
this.subscribers = EMPTY;
}
@Override
public void onSubscribe(Subscription s) {
if(buffer.isDone()){
s.cancel();
}
else if (Operators.setOnce(S, this, s)) {
ReplaySubscription[] subs = subscribers;
//first check if there are no early subscribers,
// in which case we fallback to old UNBOUNDED behavior
if (subs.length == 0) {
unbounded = true;
s.request(Long.MAX_VALUE);
return;
}
//otherwise check each early subscriber. if fused or unbounded request,
// also fallback to UNBOUNDED behavior. Apply a minimum of `parent.history`
long max = parent.history;
for (ReplaySubscription subscriber : subscribers) {
max = Math.max(subscriber.fusionMode() != Fuseable.NONE ? Long.MAX_VALUE : subscriber.signalConnectAndGetRequested(), max);
if (max == Long.MAX_VALUE) {
unbounded = true;
break;
}
}
s.request(max);
}
}
void propagateRequest(long n) {
Subscription s = S.get(this);
if (!unbounded && s != null) {
if (n == Long.MAX_VALUE) {
unbounded = true;
s.request(n);
}
else {
//TODO find a way to avoid requesting if a competing early subscriber did already request?
s.request(n);
}
}
}
@Override
public void onNext(T t) {
ReplayBuffer b = buffer;
if (b.isDone()) {
Operators.onNextDropped(t, currentContext());
}
else {
b.add(t);
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() {
if (cancelled) {
return;
}
if (Operators.terminate(S, this)) {
cancelled = true;
CONNECTION.lazySet(parent, null);
CancellationException ex = new CancellationException("Disconnected");
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 connected == 0 && CONNECTED.compareAndSet(this, 0, 1);
}
@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 cancelled;
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 cancelled;
}
}
static final class ReplayInner
implements ReplaySubscription {
final CoreSubscriber super T> actual;
final ReplaySubscriber parent;
int index;
int tailIndex;
Object node;
int fusionMode;
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");
volatile int state;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater STATE = AtomicIntegerFieldUpdater.newUpdater(ReplayInner.class, "state");
ReplayInner(CoreSubscriber super T> actual, ReplaySubscriber parent, boolean registeredBeforeConnection) {
this.actual = actual;
this.parent = parent;
this.state = registeredBeforeConnection ? STATE_EARLY_ACCUMULATE : STATE_LATE;
}
static final int STATE_LATE = 0;
static final int STATE_EARLY_ACCUMULATE = 1;
static final int STATE_EARLY_PROPAGATE = 2;
@Override
public void request(long n) {
if (Operators.validate(n)) {
if (STATE.get(this) == STATE_EARLY_ACCUMULATE) {
Operators.addCapCancellable(REQUESTED, this, n);
return;
}
if (STATE.get(this) == STATE_EARLY_PROPAGATE) {
parent.propagateRequest(n);
}
if (fusionMode() == NONE) {
Operators.addCapCancellable(REQUESTED, this, 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.get(this);
}
@Override
public long signalConnectAndGetRequested() {
STATE.set(this, STATE_EARLY_PROPAGATE);
return REQUESTED.get(this);
}
@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 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 - 2025 Weber Informatics LLC | Privacy Policy