Skip to content

Commit df7f1cd

Browse files
authored
3.x: Fix Flowable.window (size, time) cancellation and abandonment (#6758)
1 parent c045188 commit df7f1cd

File tree

6 files changed

+944
-582
lines changed

6 files changed

+944
-582
lines changed

src/main/java/io/reactivex/rxjava3/core/Flowable.java

+64
Original file line numberDiff line numberDiff line change
@@ -17407,6 +17407,10 @@ public final Flowable<T> unsubscribeOn(Scheduler scheduler) {
1740717407
* propagates the notification from the source Publisher.
1740817408
* <p>
1740917409
* <img width="640" height="400" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window3.png" alt="">
17410+
* <p>
17411+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17412+
* so-called window abandonment where a window will only contain one element. The behavior is
17413+
* a tradeoff between no-dataloss and ensuring upstream cancellation can happen.
1741017414
* <dl>
1741117415
* <dt><b>Backpressure:</b></dt>
1741217416
* <dd>The operator honors backpressure of its inner and outer subscribers, however, the inner Publisher uses an
@@ -17436,6 +17440,11 @@ public final Flowable<Flowable<T>> window(long count) {
1743617440
* and propagates the notification from the source Publisher.
1743717441
* <p>
1743817442
* <img width="640" height="365" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window4.png" alt="">
17443+
* <p>
17444+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17445+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17446+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17447+
* a tradeoff between no-dataloss and ensuring upstream cancellation can happen under some race conditions.
1743917448
* <dl>
1744017449
* <dt><b>Backpressure:</b></dt>
1744117450
* <dd>The operator honors backpressure of its inner and outer subscribers, however, the inner Publisher uses an
@@ -17468,6 +17477,11 @@ public final Flowable<Flowable<T>> window(long count, long skip) {
1746817477
* and propagates the notification from the source Publisher.
1746917478
* <p>
1747017479
* <img width="640" height="365" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window4.png" alt="">
17480+
* <p>
17481+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17482+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17483+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17484+
* a tradeoff between no-dataloss and ensuring upstream cancellation can happen under some race conditions.
1747117485
* <dl>
1747217486
* <dt><b>Backpressure:</b></dt>
1747317487
* <dd>The operator honors backpressure of its inner and outer subscribers, however, the inner Publisher uses an
@@ -17506,6 +17520,11 @@ public final Flowable<Flowable<T>> window(long count, long skip, int bufferSize)
1750617520
* current window and propagates the notification from the source Publisher.
1750717521
* <p>
1750817522
* <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window7.png" alt="">
17523+
* <p>
17524+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17525+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17526+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17527+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1750917528
* <dl>
1751017529
* <dt><b>Backpressure:</b></dt>
1751117530
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.
@@ -17541,6 +17560,11 @@ public final Flowable<Flowable<T>> window(long timespan, long timeskip, TimeUnit
1754117560
* current window and propagates the notification from the source Publisher.
1754217561
* <p>
1754317562
* <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window7.s.png" alt="">
17563+
* <p>
17564+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17565+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17566+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17567+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1754417568
* <dl>
1754517569
* <dt><b>Backpressure:</b></dt>
1754617570
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.
@@ -17578,6 +17602,11 @@ public final Flowable<Flowable<T>> window(long timespan, long timeskip, TimeUnit
1757817602
* current window and propagates the notification from the source Publisher.
1757917603
* <p>
1758017604
* <img width="640" height="335" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window7.s.png" alt="">
17605+
* <p>
17606+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17607+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17608+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17609+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1758117610
* <dl>
1758217611
* <dt><b>Backpressure:</b></dt>
1758317612
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.
@@ -17622,6 +17651,11 @@ public final Flowable<Flowable<T>> window(long timespan, long timeskip, TimeUnit
1762217651
* Publisher emits the current window and propagates the notification from the source Publisher.
1762317652
* <p>
1762417653
* <img width="640" height="375" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window5.png" alt="">
17654+
* <p>
17655+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17656+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17657+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17658+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1762517659
* <dl>
1762617660
* <dt><b>Backpressure:</b></dt>
1762717661
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.
@@ -17656,6 +17690,11 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit) {
1765617690
* emits the current window and propagates the notification from the source Publisher.
1765717691
* <p>
1765817692
* <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window6.png" alt="">
17693+
* <p>
17694+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17695+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17696+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17697+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1765917698
* <dl>
1766017699
* <dt><b>Backpressure:</b></dt>
1766117700
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.
@@ -17694,6 +17733,11 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit,
1769417733
* emits the current window and propagates the notification from the source Publisher.
1769517734
* <p>
1769617735
* <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window6.png" alt="">
17736+
* <p>
17737+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17738+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17739+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17740+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1769717741
* <dl>
1769817742
* <dt><b>Backpressure:</b></dt>
1769917743
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.
@@ -17733,6 +17777,11 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit,
1773317777
* Publisher emits the current window and propagates the notification from the source Publisher.
1773417778
* <p>
1773517779
* <img width="640" height="375" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window5.s.png" alt="">
17780+
* <p>
17781+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17782+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17783+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17784+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1773617785
* <dl>
1773717786
* <dt><b>Backpressure:</b></dt>
1773817787
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.
@@ -17771,6 +17820,11 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit,
1777117820
* current window and propagates the notification from the source Publisher.
1777217821
* <p>
1777317822
* <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window6.s.png" alt="">
17823+
* <p>
17824+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17825+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17826+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17827+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1777417828
* <dl>
1777517829
* <dt><b>Backpressure:</b></dt>
1777617830
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.
@@ -17811,6 +17865,11 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit,
1781117865
* current window and propagates the notification from the source Publisher.
1781217866
* <p>
1781317867
* <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window6.s.png" alt="">
17868+
* <p>
17869+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17870+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17871+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17872+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1781417873
* <dl>
1781517874
* <dt><b>Backpressure:</b></dt>
1781617875
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.
@@ -17853,6 +17912,11 @@ public final Flowable<Flowable<T>> window(long timespan, TimeUnit unit,
1785317912
* current window and propagates the notification from the source Publisher.
1785417913
* <p>
1785517914
* <img width="640" height="370" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window6.s.png" alt="">
17915+
* <p>
17916+
* Note that ignoring windows or subscribing later (i.e., on another thread) will result in
17917+
* so-called window abandonment where a window may not contain any elements. In this case, subsequent
17918+
* elements will be dropped until the condition for the next window boundary is satisfied. The behavior is
17919+
* a tradeoff for ensuring upstream cancellation can happen under some race conditions.
1785617920
* <dl>
1785717921
* <dt><b>Backpressure:</b></dt>
1785817922
* <dd>The operator consumes the source {@code Publisher} in an unbounded manner.

src/main/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableWindow.java

+65-34
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import io.reactivex.rxjava3.internal.subscriptions.SubscriptionHelper;
2424
import io.reactivex.rxjava3.internal.util.BackpressureHelper;
2525
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
26-
import io.reactivex.rxjava3.processors.UnicastProcessor;
26+
import io.reactivex.rxjava3.processors.*;
2727

2828
public final class FlowableWindow<T> extends AbstractFlowableWithUpstream<T, Flowable<T>> {
2929
final long size;
@@ -92,13 +92,15 @@ public void onNext(T t) {
9292
long i = index;
9393

9494
UnicastProcessor<T> w = window;
95+
WindowSubscribeIntercept<T> intercept = null;
9596
if (i == 0) {
9697
getAndIncrement();
9798

9899
w = UnicastProcessor.<T>create(bufferSize, this);
99100
window = w;
100101

101-
downstream.onNext(w);
102+
intercept = new WindowSubscribeIntercept<T>(w);
103+
downstream.onNext(intercept);
102104
}
103105

104106
i++;
@@ -112,6 +114,10 @@ public void onNext(T t) {
112114
} else {
113115
index = i;
114116
}
117+
118+
if (intercept != null && intercept.tryAbandon()) {
119+
intercept.window.onComplete();
120+
}
115121
}
116122

117123
@Override
@@ -205,14 +211,16 @@ public void onSubscribe(Subscription s) {
205211
public void onNext(T t) {
206212
long i = index;
207213

214+
WindowSubscribeIntercept<T> intercept = null;
208215
UnicastProcessor<T> w = window;
209216
if (i == 0) {
210217
getAndIncrement();
211218

212219
w = UnicastProcessor.<T>create(bufferSize, this);
213220
window = w;
214221

215-
downstream.onNext(w);
222+
intercept = new WindowSubscribeIntercept<T>(w);
223+
downstream.onNext(intercept);
216224
}
217225

218226
i++;
@@ -231,6 +239,10 @@ public void onNext(T t) {
231239
} else {
232240
index = i;
233241
}
242+
243+
if (intercept != null && intercept.tryAbandon()) {
244+
intercept.window.onComplete();
245+
}
234246
}
235247

236248
@Override
@@ -352,16 +364,14 @@ public void onNext(T t) {
352364

353365
long i = index;
354366

367+
UnicastProcessor<T> newWindow = null;
355368
if (i == 0) {
356369
if (!cancelled) {
357370
getAndIncrement();
358371

359-
UnicastProcessor<T> w = UnicastProcessor.<T>create(bufferSize, this);
360-
361-
windows.offer(w);
372+
newWindow = UnicastProcessor.<T>create(bufferSize, this);
362373

363-
queue.offer(w);
364-
drain();
374+
windows.offer(newWindow);
365375
}
366376
}
367377

@@ -371,6 +381,11 @@ public void onNext(T t) {
371381
w.onNext(t);
372382
}
373383

384+
if (newWindow != null) {
385+
queue.offer(newWindow);
386+
drain();
387+
}
388+
374389
long p = produced + 1;
375390
if (p == size) {
376391
produced = p - skip;
@@ -431,39 +446,59 @@ void drain() {
431446
final SpscLinkedArrayQueue<UnicastProcessor<T>> q = queue;
432447
int missed = 1;
433448

449+
outer:
434450
for (;;) {
435451

436-
long r = requested.get();
437-
long e = 0;
452+
if (cancelled) {
453+
UnicastProcessor<T> up = null;
454+
while ((up = q.poll()) != null) {
455+
up.onComplete();
456+
}
457+
} else {
458+
long r = requested.get();
459+
long e = 0;
438460

439-
while (e != r) {
440-
boolean d = done;
461+
while (e != r) {
462+
boolean d = done;
441463

442-
UnicastProcessor<T> t = q.poll();
464+
UnicastProcessor<T> t = q.poll();
443465

444-
boolean empty = t == null;
466+
boolean empty = t == null;
445467

446-
if (checkTerminated(d, empty, a, q)) {
447-
return;
448-
}
468+
if (cancelled) {
469+
continue outer;
470+
}
449471

450-
if (empty) {
451-
break;
452-
}
472+
if (checkTerminated(d, empty, a, q)) {
473+
return;
474+
}
453475

454-
a.onNext(t);
476+
if (empty) {
477+
break;
478+
}
455479

456-
e++;
457-
}
480+
WindowSubscribeIntercept<T> intercept = new WindowSubscribeIntercept<T>(t);
481+
a.onNext(intercept);
458482

459-
if (e == r) {
460-
if (checkTerminated(done, q.isEmpty(), a, q)) {
461-
return;
483+
if (intercept.tryAbandon()) {
484+
t.onComplete();
485+
}
486+
e++;
487+
}
488+
489+
if (e == r) {
490+
if (cancelled) {
491+
continue outer;
492+
}
493+
494+
if (checkTerminated(done, q.isEmpty(), a, q)) {
495+
return;
496+
}
462497
}
463-
}
464498

465-
if (e != 0L && r != Long.MAX_VALUE) {
466-
requested.addAndGet(-e);
499+
if (e != 0L && r != Long.MAX_VALUE) {
500+
requested.addAndGet(-e);
501+
}
467502
}
468503

469504
missed = wip.addAndGet(-missed);
@@ -474,11 +509,6 @@ void drain() {
474509
}
475510

476511
boolean checkTerminated(boolean d, boolean empty, Subscriber<?> a, SpscLinkedArrayQueue<?> q) {
477-
if (cancelled) {
478-
q.clear();
479-
return true;
480-
}
481-
482512
if (d) {
483513
Throwable e = error;
484514

@@ -520,6 +550,7 @@ public void cancel() {
520550
if (once.compareAndSet(false, true)) {
521551
run();
522552
}
553+
drain();
523554
}
524555

525556
@Override

0 commit comments

Comments
 (0)