reactor.core.publisher.FluxWindowPredicate 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.Queue;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.Disposable;
import reactor.core.Exceptions;
import reactor.core.Fuseable;
import reactor.core.Scannable;
import reactor.core.publisher.FluxBufferPredicate.Mode;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;
import static reactor.core.Exceptions.wrapSource;
/**
* Cut a sequence into non-overlapping windows where each window boundary is determined by
* a {@link Predicate} on the values. The predicate can be used in several modes:
*
* - {@code Until}: A new window starts when the predicate returns true. The
* element that just matched the predicate is the last in the previous window, and the
* windows are not emitted before an inner element is pushed.
* - {@code UntilOther}: A new window starts when the predicate returns true. The
* element that just matched the predicate is the first in the new window, which is
* emitted immediately.
* - {@code While}: A new window starts when the predicate stops matching. The
* non-matching elements that delimit each window are simply discarded, and the
* windows are not emitted before an inner element is pushed
*
*
* @param the source and window value type
*
* @see Reactive-Streams-Commons
*/
final class FluxWindowPredicate extends InternalFluxOperator>
implements Fuseable{
final Supplier extends Queue> groupQueueSupplier;
final Supplier extends Queue>> mainQueueSupplier;
final Mode mode;
final Predicate super T> predicate;
final int prefetch;
FluxWindowPredicate(Flux extends T> source,
Supplier extends Queue>> mainQueueSupplier,
Supplier extends Queue> groupQueueSupplier,
int prefetch,
Predicate super T> predicate,
Mode mode) {
super(Flux.from(source));
if (prefetch <= 0) {
throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch);
}
this.predicate = Objects.requireNonNull(predicate, "predicate");
this.mainQueueSupplier =
Objects.requireNonNull(mainQueueSupplier, "mainQueueSupplier");
this.groupQueueSupplier =
Objects.requireNonNull(groupQueueSupplier, "groupQueueSupplier");
this.mode = mode;
this.prefetch = prefetch;
}
@Override
public CoreSubscriber super T> subscribeOrReturn(CoreSubscriber super Flux> actual) {
return new WindowPredicateMain<>(actual,
mainQueueSupplier.get(),
groupQueueSupplier,
prefetch,
predicate,
mode);
}
@Override
public int getPrefetch() {
return prefetch;
}
@Override
public Object scanUnsafe(Attr key) {
if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC;
return super.scanUnsafe(key);
}
static final class WindowPredicateMain
implements Fuseable.QueueSubscription>,
InnerOperator> {
final CoreSubscriber super Flux> actual;
final Context ctx;
final Supplier extends Queue> groupQueueSupplier;
final Mode mode;
final Predicate super T> predicate;
final int prefetch;
final Queue> queue;
WindowFlux window;
volatile int wip;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater WIP =
AtomicIntegerFieldUpdater.newUpdater(WindowPredicateMain.class, "wip");
volatile long requested;
@SuppressWarnings("rawtypes")
static final AtomicLongFieldUpdater REQUESTED =
AtomicLongFieldUpdater.newUpdater(WindowPredicateMain.class, "requested");
volatile boolean done;
volatile Throwable error;
@SuppressWarnings("rawtypes")
static final AtomicReferenceFieldUpdater ERROR =
AtomicReferenceFieldUpdater.newUpdater(WindowPredicateMain.class,
Throwable.class,
"error");
volatile int cancelled;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater CANCELLED =
AtomicIntegerFieldUpdater.newUpdater(WindowPredicateMain.class,
"cancelled");
volatile int windowCount;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater WINDOW_COUNT =
AtomicIntegerFieldUpdater.newUpdater(WindowPredicateMain.class, "windowCount");
Subscription s;
volatile boolean outputFused;
WindowPredicateMain(CoreSubscriber super Flux> actual,
Queue> queue,
Supplier extends Queue> groupQueueSupplier,
int prefetch,
Predicate super T> predicate,
Mode mode) {
this.actual = actual;
this.ctx = actual.currentContext();
this.queue = queue;
this.groupQueueSupplier = groupQueueSupplier;
this.prefetch = prefetch;
this.predicate = predicate;
this.mode = mode;
WINDOW_COUNT.lazySet(this, 2);
initializeWindow();
}
@Override
public void onSubscribe(Subscription s) {
if (Operators.validate(this.s, s)) {
this.s = s;
actual.onSubscribe(this);
if (cancelled == 0) {
s.request(Operators.unboundedOrPrefetch(prefetch));
}
}
}
void initializeWindow() {
WindowFlux g = new WindowFlux<>(
groupQueueSupplier.get(),
this);
window = g;
}
@Nullable WindowFlux newWindowDeferred() {
// if the main is cancelled, don't create new groups
if (cancelled == 0) {
WINDOW_COUNT.getAndIncrement(this);
WindowFlux g = new WindowFlux<>(groupQueueSupplier.get(), this);
window = g;
return g;
}
return null;
}
@Override
public void onNext(T t) {
if (done) {
Operators.onNextDropped(t, ctx);
return;
}
WindowFlux g = window;
boolean match;
try {
match = predicate.test(t);
}
catch (Throwable e) {
onError(Operators.onOperatorError(s, e, t, ctx));
return;
}
if (!handleDeferredWindow(g, t)) {
return;
}
drain();
if (mode == Mode.UNTIL && match) {
if(g.cancelled) {
// so it's consistent with the previously discarded elements when (mode == UNTIL && !match) was satisfied
Operators.onDiscard(t, ctx);
s.request(1);
} else {
g.onNext(t);
}
g.onComplete();
newWindowDeferred();
s.request(1);
}
else if (mode == Mode.UNTIL_CUT_BEFORE && match) {
g.onComplete();
g = newWindowDeferred();
if (g != null) {
g.onNext(t);
handleDeferredWindow(g, t);
drain();
}
}
else if (mode == Mode.WHILE && !match) {
g.onComplete();
newWindowDeferred();
Operators.onDiscard(t, ctx);
//compensate for the dropped delimiter
s.request(1);
}
else if(g.cancelled) {
Operators.onDiscard(t, ctx);
s.request(1);
}
else {
g.onNext(t);
}
}
boolean handleDeferredWindow(@Nullable WindowFlux window, T signal) {
if (window != null && window.deferred) {
window.deferred = false;
if (!queue.offer(window)) {
onError(Operators.onOperatorError(this,
Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL),
signal,
ctx));
return false;
}
}
return true;
}
@Override
public void onError(Throwable t) {
if (Exceptions.addThrowable(ERROR, this, t)) {
done = true;
cleanup();
drain();
}
else {
Operators.onErrorDropped(t, ctx);
}
}
@Override
public void onComplete() {
if(done) {
return;
}
cleanup();
WindowFlux g = window;
if (g != null) {
g.onComplete();
}
window = null;
done = true;
WINDOW_COUNT.decrementAndGet(this);
drain();
}
void cleanup() {
// necessary cleanup if predicate contains a state
if (predicate instanceof Disposable) {
((Disposable) predicate).dispose();
}
}
@Override
@Nullable
public Object scanUnsafe(Attr key) {
if (key == Attr.PARENT) return s;
if (key == Attr.CANCELLED) return cancelled == 1;
if (key == Attr.PREFETCH) return prefetch;
if (key == Attr.TERMINATED) return done;
if (key == Attr.BUFFERED) return queue.size();
if (key == Attr.ERROR) return error;
if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested;
if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC;
return InnerOperator.super.scanUnsafe(key);
}
@Override
public Stream extends Scannable> inners() {
return window == null ? Stream.empty() : Stream.of(window);
}
@Override
public CoreSubscriber super Flux> actual() {
return actual;
}
void signalAsyncError() {
Throwable e = Exceptions.terminate(ERROR, this);
windowCount = 0;
WindowFlux g = window;
if (g != null) {
g.onError(wrapSource(e));
}
actual.onError(e);
window = null;
}
@Override
public void request(long n) {
if (Operators.validate(n)) {
Operators.addCap(REQUESTED, this, n);
drain();
}
}
@Override
public void cancel() {
if (CANCELLED.compareAndSet(this, 0, 1)) {
if (WINDOW_COUNT.decrementAndGet(this) == 0) {
s.cancel();
cleanup();
}
else if (!outputFused) {
if (WIP.getAndIncrement(this) == 0) {
// remove queued up but unobservable groups from the mapping
Flux g;
WindowFlux w = window;
while ((g = queue.poll()) != null) {
((WindowFlux) g).cancel();
}
if (w != null && w.deferred) {
w.cancel();
}
if (WIP.decrementAndGet(this) == 0) {
if (!done && WINDOW_COUNT.get(this) == 0) {
s.cancel();
cleanup();
}
else {
CANCELLED.set(this, 2);
}
return;
}
CANCELLED.set(this, 2); //in flight windows might get cancelled still
drainLoop();
}
}
}
else if (CANCELLED.get(this) == 2) {
//no new window should have been created
if (WINDOW_COUNT.get(this) == 0) {
s.cancel();
cleanup();
}
//next one to call cancel in state 2 that decrements to 0 will cancel outer
}
}
void groupTerminated() {
if (windowCount == 0) {
return;
}
window = null;
if (WINDOW_COUNT.decrementAndGet(this) == 0) {
s.cancel();
cleanup();
}
}
void drain() {
if (WIP.getAndIncrement(this) != 0) {
return;
}
if (outputFused) {
drainFused();
}
else {
drainLoop();
}
}
void drainFused() {
int missed = 1;
final Subscriber super Flux> a = actual;
final Queue> q = queue;
for (; ; ) {
if (cancelled != 0) {
q.clear();
return;
}
boolean d = done;
a.onNext(null);
if (d) {
Throwable ex = error;
if (ex != null) {
signalAsyncError();
}
else {
a.onComplete();
}
return;
}
missed = WIP.addAndGet(this, -missed);
if (missed == 0) {
break;
}
}
}
void drainLoop() {
int missed = 1;
Subscriber super Flux> a = actual;
Queue> q = queue;
for (; ; ) {
long r = requested;
long e = 0L;
while (e != r) {
boolean d = done;
Flux v = q.poll();
boolean empty = v == null;
if (checkTerminated(d, empty, a, q)) {
return;
}
if (empty) {
break;
}
a.onNext(v);
e++;
}
if (e == r) {
if (checkTerminated(done, q.isEmpty(), a, q)) {
return;
}
}
if (e != 0L) {
s.request(e);
if (r != Long.MAX_VALUE) {
REQUESTED.addAndGet(this, -e);
}
}
missed = WIP.addAndGet(this, -missed);
if (missed == 0) {
break;
}
}
}
boolean checkTerminated(boolean d,
boolean empty,
Subscriber> a,
Queue> q) {
if (cancelled != 0) {
q.clear();
return true;
}
if (d) {
Throwable e = error;
if (e != null && e != Exceptions.TERMINATED) {
queue.clear();
signalAsyncError();
return true;
}
else if (empty) {
a.onComplete();
return true;
}
}
return false;
}
@Override
@Nullable
public Flux poll() {
return queue.poll();
}
@Override
public int size() {
return queue.size();
}
@Override
public boolean isEmpty() {
return queue.isEmpty();
}
@Override
public void clear() {
queue.clear();
}
@Override
public int requestFusion(int requestedMode) {
if ((requestedMode & Fuseable.ASYNC) != 0) {
outputFused = true;
return Fuseable.ASYNC;
}
return Fuseable.NONE;
}
}
static final class WindowFlux extends Flux
implements Fuseable, Fuseable.QueueSubscription, InnerOperator {
final Queue queue;
volatile WindowPredicateMain parent;
@SuppressWarnings("rawtypes")
static final AtomicReferenceFieldUpdater
PARENT = AtomicReferenceFieldUpdater.newUpdater(WindowFlux.class,
WindowPredicateMain.class,
"parent");
volatile boolean done;
Throwable error;
volatile CoreSubscriber super T> actual;
@SuppressWarnings("rawtypes")
static final AtomicReferenceFieldUpdater ACTUAL =
AtomicReferenceFieldUpdater.newUpdater(WindowFlux.class,
CoreSubscriber.class,
"actual");
volatile Context ctx = Context.empty();
volatile boolean cancelled;
volatile int once;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater ONCE =
AtomicIntegerFieldUpdater.newUpdater(WindowFlux.class, "once");
volatile int wip;
@SuppressWarnings("rawtypes")
static final AtomicIntegerFieldUpdater WIP =
AtomicIntegerFieldUpdater.newUpdater(WindowFlux.class, "wip");
volatile long requested;
@SuppressWarnings("rawtypes")
static final AtomicLongFieldUpdater REQUESTED =
AtomicLongFieldUpdater.newUpdater(WindowFlux.class, "requested");
volatile boolean enableOperatorFusion;
int produced;
boolean deferred;
WindowFlux(
Queue queue,
WindowPredicateMain parent) {
this.queue = queue;
this.parent = parent;
this.deferred = true;
}
@Override
public CoreSubscriber super T> actual() {
return actual;
}
void propagateTerminate() {
WindowPredicateMain r = parent;
if (r != null && PARENT.compareAndSet(this, r, null)) {
r.groupTerminated();
}
}
void drainRegular(Subscriber super T> a) {
int missed = 1;
final Queue q = queue;
for (; ; ) {
long r = requested;
long e = 0L;
while (r != e) {
boolean d = done;
T t = q.poll();
boolean empty = t == null;
if (checkTerminated(d, empty, a, q)) {
return;
}
if (empty) {
break;
}
a.onNext(t);
e++;
}
if (r == e) {
if (checkTerminated(done, q.isEmpty(), a, q)) {
return;
}
}
if (e != 0) {
WindowPredicateMain main = parent;
if (main != null) {
main.s.request(e);
}
if (r != Long.MAX_VALUE) {
REQUESTED.addAndGet(this, -e);
}
}
missed = WIP.addAndGet(this, -missed);
if (missed == 0) {
break;
}
}
}
void drainFused(Subscriber super T> a) {
int missed = 1;
final Queue q = queue;
for (; ; ) {
if (cancelled) {
Operators.onDiscardQueueWithClear(q, ctx, null);
ctx = Context.empty();
actual = null;
return;
}
boolean d = done;
a.onNext(null);
if (d) {
ctx = Context.empty();
actual = null;
Throwable ex = error;
if (ex != null) {
a.onError(ex);
}
else {
a.onComplete();
}
return;
}
missed = WIP.addAndGet(this, -missed);
if (missed == 0) {
break;
}
}
}
void drain() {
Subscriber super T> a = actual;
if (a != null) {
if (WIP.getAndIncrement(this) != 0) {
return;
}
if (enableOperatorFusion) {
drainFused(a);
}
else {
drainRegular(a);
}
}
}
boolean checkTerminated(boolean d, boolean empty, Subscriber> a, Queue> q) {
if (cancelled) {
Operators.onDiscardQueueWithClear(q, ctx, null);
ctx = Context.empty();
actual = null;
return true;
}
if (d && empty) {
Throwable e = error;
ctx = Context.empty();
actual = null;
if (e != null) {
a.onError(e);
}
else {
a.onComplete();
}
return true;
}
return false;
}
public void onNext(T t) {
Subscriber super T> a = actual;
if (!queue.offer(t)) {
onError(Operators.onOperatorError(this, Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL), t,
ctx));
return;
}
if (enableOperatorFusion) {
if (a != null) {
a.onNext(null); // in op-fusion, onNext(null) is the indicator of more data
}
}
else {
drain();
}
}
@Override
public void onSubscribe(Subscription s) {
//IGNORE
}
@Override
public void onError(Throwable t) {
error = t;
done = true;
propagateTerminate();
drain();
}
@Override
public void onComplete() {
done = true;
propagateTerminate();
drain();
}
@Override
public void subscribe(CoreSubscriber super T> actual) {
if (once == 0 && ONCE.compareAndSet(this, 0, 1)) {
actual.onSubscribe(this);
ACTUAL.lazySet(this, actual);
ctx = actual.currentContext();
drain();
}
else {
actual.onError(new IllegalStateException(
"This processor allows only a single Subscriber"));
}
}
@Override
public void request(long n) {
if (Operators.validate(n)) {
Operators.addCap(REQUESTED, this, n);
drain();
}
}
@Override
public void cancel() {
if (cancelled) {
return;
}
cancelled = true;
WindowPredicateMain r = parent;
if (r != null &&
PARENT.compareAndSet(this, r, null)) {
if (WindowPredicateMain.WINDOW_COUNT.decrementAndGet(r) == 0) {
r.cancel();
}
else {
r.s.request(1);
}
}
if (!enableOperatorFusion) {
if (WIP.getAndIncrement(this) == 0) {
Operators.onDiscardQueueWithClear(queue, ctx, null);
}
}
}
@Override
@Nullable
public T poll() {
T v = queue.poll();
if (v != null) {
produced++;
}
else {
int p = produced;
if (p != 0) {
produced = 0;
WindowPredicateMain main = parent;
if (main != null) {
main.s.request(p);
}
}
}
return v;
}
@Override
public int size() {
return queue.size();
}
@Override
public boolean isEmpty() {
return queue.isEmpty();
}
@Override
public void clear() {
Operators.onDiscardQueueWithClear(queue, ctx, null);
}
@Override
public int requestFusion(int requestedMode) {
if ((requestedMode & Fuseable.ASYNC) != 0) {
enableOperatorFusion = true;
return Fuseable.ASYNC;
}
return Fuseable.NONE;
}
@Override
@Nullable
public Object scanUnsafe(Attr key) {
if (key == Attr.PARENT) return parent;
if (key == Attr.CANCELLED) return cancelled;
if (key == Attr.TERMINATED) return done;
if (key == Attr.BUFFERED) return queue == null ? 0 : queue.size();
if (key == Attr.ERROR) return error;
if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return requested;
if (key == Attr.RUN_STYLE) return Attr.RunStyle.SYNC;
return InnerOperator.super.scanUnsafe(key);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy