Skip to content

Commit ea7ca2c

Browse files
authored
2.x: fix Obs.combineLatestDelayError sync initial error not emitting (#5560)
* 2.x: fix Obs.combineLatestDelayError sync initial error not emitting * Remove unused method.
1 parent 357fac2 commit ea7ca2c

File tree

3 files changed

+142
-114
lines changed

3 files changed

+142
-114
lines changed

src/main/java/io/reactivex/internal/operators/observable/ObservableCombineLatest.java

Lines changed: 104 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
package io.reactivex.internal.operators.observable;
1515

16-
import java.util.Arrays;
1716
import java.util.concurrent.atomic.*;
1817

1918
import io.reactivex.*;
@@ -79,8 +78,8 @@ static final class LatestCoordinator<T, R> extends AtomicInteger implements Disp
7978
final Observer<? super R> actual;
8079
final Function<? super Object[], ? extends R> combiner;
8180
final CombinerObserver<T, R>[] observers;
82-
final T[] latest;
83-
final SpscLinkedArrayQueue<Object> queue;
81+
Object[] latest;
82+
final SpscLinkedArrayQueue<Object[]> queue;
8483
final boolean delayError;
8584

8685
volatile boolean cancelled;
@@ -99,18 +98,18 @@ static final class LatestCoordinator<T, R> extends AtomicInteger implements Disp
9998
this.actual = actual;
10099
this.combiner = combiner;
101100
this.delayError = delayError;
102-
this.latest = (T[])new Object[count];
103-
this.observers = new CombinerObserver[count];
104-
this.queue = new SpscLinkedArrayQueue<Object>(bufferSize);
101+
this.latest = new Object[count];
102+
CombinerObserver<T, R>[] as = new CombinerObserver[count];
103+
for (int i = 0; i < count; i++) {
104+
as[i] = new CombinerObserver<T, R>(this, i);
105+
}
106+
this.observers = as;
107+
this.queue = new SpscLinkedArrayQueue<Object[]>(bufferSize);
105108
}
106109

107110
public void subscribe(ObservableSource<? extends T>[] sources) {
108111
Observer<T>[] as = observers;
109112
int len = as.length;
110-
for (int i = 0; i < len; i++) {
111-
as[i] = new CombinerObserver<T, R>(this, i);
112-
}
113-
lazySet(0); // release array contents
114113
actual.onSubscribe(this);
115114
for (int i = 0; i < len; i++) {
116115
if (done || cancelled) {
@@ -136,11 +135,6 @@ public boolean isDisposed() {
136135
return cancelled;
137136
}
138137

139-
void cancel(SpscLinkedArrayQueue<?> q) {
140-
clear(q);
141-
cancelSources();
142-
}
143-
144138
void cancelSources() {
145139
for (CombinerObserver<T, R> s : observers) {
146140
s.dispose();
@@ -149,96 +143,65 @@ void cancelSources() {
149143

150144
void clear(SpscLinkedArrayQueue<?> q) {
151145
synchronized (this) {
152-
Arrays.fill(latest, null);
146+
latest = null;
153147
}
154148
q.clear();
155149
}
156150

157-
void combine(T value, int index) {
158-
CombinerObserver<T, R> cs = observers[index];
159-
160-
int a;
161-
int c;
162-
int len;
163-
boolean empty;
164-
boolean f;
165-
synchronized (this) {
166-
if (cancelled) {
167-
return;
168-
}
169-
len = latest.length;
170-
T o = latest[index];
171-
a = active;
172-
if (o == null) {
173-
active = ++a;
174-
}
175-
c = complete;
176-
if (value == null) {
177-
complete = ++c;
178-
} else {
179-
latest[index] = value;
180-
}
181-
f = a == len;
182-
// see if either all sources completed
183-
empty = c == len
184-
|| (value == null && o == null); // or this source completed without any value
185-
if (!empty) {
186-
if (value != null && f) {
187-
queue.offer(cs, latest.clone());
188-
} else
189-
if (value == null && errors.get() != null) {
190-
done = true; // if this source completed without a value
191-
}
192-
} else {
193-
done = true;
194-
}
195-
}
196-
if (!f && value != null) {
197-
return;
198-
}
199-
drain();
200-
}
201151
void drain() {
202152
if (getAndIncrement() != 0) {
203153
return;
204154
}
205155

206-
final SpscLinkedArrayQueue<Object> q = queue;
156+
final SpscLinkedArrayQueue<Object[]> q = queue;
207157
final Observer<? super R> a = actual;
208158
final boolean delayError = this.delayError;
209159

210160
int missed = 1;
211161
for (;;) {
212162

213-
if (checkTerminated(done, q.isEmpty(), a, q, delayError)) {
214-
return;
215-
}
216-
217163
for (;;) {
164+
if (cancelled) {
165+
clear(q);
166+
return;
167+
}
168+
169+
if (!delayError && errors.get() != null) {
170+
cancelSources();
171+
clear(q);
172+
a.onError(errors.terminate());
173+
return;
174+
}
218175

219176
boolean d = done;
220-
@SuppressWarnings("unchecked")
221-
CombinerObserver<T, R> cs = (CombinerObserver<T, R>)q.poll();
222-
boolean empty = cs == null;
177+
Object[] s = q.poll();
178+
boolean empty = s == null;
223179

224-
if (checkTerminated(d, empty, a, q, delayError)) {
180+
if (d && empty) {
181+
clear(q);
182+
Throwable ex = errors.terminate();
183+
if (ex == null) {
184+
a.onComplete();
185+
} else {
186+
a.onError(ex);
187+
}
225188
return;
226189
}
227190

228191
if (empty) {
229192
break;
230193
}
231194

232-
@SuppressWarnings("unchecked")
233-
T[] array = (T[])q.poll();
234-
235195
R v;
196+
236197
try {
237-
v = ObjectHelper.requireNonNull(combiner.apply(array), "The combiner returned a null");
198+
v = ObjectHelper.requireNonNull(combiner.apply(s), "The combiner returned a null value");
238199
} catch (Throwable ex) {
239200
Exceptions.throwIfFatal(ex);
240-
cancelled = true;
241-
cancel(q);
201+
errors.addThrowable(ex);
202+
cancelSources();
203+
clear(q);
204+
ex = errors.terminate();
242205
a.onError(ex);
243206
return;
244207
}
@@ -253,53 +216,81 @@ void drain() {
253216
}
254217
}
255218

256-
257-
boolean checkTerminated(boolean d, boolean empty, Observer<?> a, SpscLinkedArrayQueue<?> q, boolean delayError) {
258-
if (cancelled) {
259-
cancel(q);
260-
return true;
219+
void innerNext(int index, T item) {
220+
boolean shouldDrain = false;
221+
synchronized (this) {
222+
Object[] latest = this.latest;
223+
if (latest == null) {
224+
return;
225+
}
226+
Object o = latest[index];
227+
int a = active;
228+
if (o == null) {
229+
active = ++a;
230+
}
231+
latest[index] = item;
232+
if (a == latest.length) {
233+
queue.offer(latest.clone());
234+
shouldDrain = true;
235+
}
236+
}
237+
if (shouldDrain) {
238+
drain();
261239
}
262-
if (d) {
240+
}
241+
242+
void innerError(int index, Throwable ex) {
243+
if (errors.addThrowable(ex)) {
244+
boolean cancelOthers = true;
263245
if (delayError) {
264-
if (empty) {
265-
cancel(q);
266-
Throwable e = errors.terminate();
267-
if (e != null) {
268-
a.onError(e);
269-
} else {
270-
a.onComplete();
246+
synchronized (this) {
247+
Object[] latest = this.latest;
248+
if (latest == null) {
249+
return;
250+
}
251+
252+
cancelOthers = latest[index] == null;
253+
if (cancelOthers || ++complete == latest.length) {
254+
done = true;
271255
}
272-
return true;
273-
}
274-
} else {
275-
Throwable e = errors.get();
276-
if (e != null) {
277-
cancel(q);
278-
a.onError(errors.terminate());
279-
return true;
280-
} else
281-
if (empty) {
282-
clear(queue);
283-
a.onComplete();
284-
return true;
285256
}
286257
}
258+
if (cancelOthers) {
259+
cancelSources();
260+
}
261+
drain();
262+
} else {
263+
RxJavaPlugins.onError(ex);
287264
}
288-
return false;
289265
}
290266

291-
void onError(Throwable e) {
292-
if (!errors.addThrowable(e)) {
293-
RxJavaPlugins.onError(e);
267+
void innerComplete(int index) {
268+
boolean cancelOthers = false;
269+
synchronized (this) {
270+
Object[] latest = this.latest;
271+
if (latest == null) {
272+
return;
273+
}
274+
275+
cancelOthers = latest[index] == null;
276+
if (cancelOthers || ++complete == latest.length) {
277+
done = true;
278+
}
279+
}
280+
if (cancelOthers) {
281+
cancelSources();
294282
}
283+
drain();
295284
}
285+
296286
}
297287

298-
static final class CombinerObserver<T, R> implements Observer<T> {
288+
static final class CombinerObserver<T, R> extends AtomicReference<Disposable> implements Observer<T> {
289+
private static final long serialVersionUID = -4823716997131257941L;
290+
299291
final LatestCoordinator<T, R> parent;
300-
final int index;
301292

302-
final AtomicReference<Disposable> s = new AtomicReference<Disposable>();
293+
final int index;
303294

304295
CombinerObserver(LatestCoordinator<T, R> parent, int index) {
305296
this.parent = parent;
@@ -308,27 +299,26 @@ static final class CombinerObserver<T, R> implements Observer<T> {
308299

309300
@Override
310301
public void onSubscribe(Disposable s) {
311-
DisposableHelper.setOnce(this.s, s);
302+
DisposableHelper.setOnce(this, s);
312303
}
313304

314305
@Override
315306
public void onNext(T t) {
316-
parent.combine(t, index);
307+
parent.innerNext(index, t);
317308
}
318309

319310
@Override
320311
public void onError(Throwable t) {
321-
parent.onError(t);
322-
parent.combine(null, index);
312+
parent.innerError(index, t);
323313
}
324314

325315
@Override
326316
public void onComplete() {
327-
parent.combine(null, index);
317+
parent.innerComplete(index);
328318
}
329319

330320
public void dispose() {
331-
DisposableHelper.dispose(s);
321+
DisposableHelper.dispose(this);
332322
}
333323
}
334324
}

src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,4 +1569,23 @@ public Integer apply(Integer t1, Integer t2) throws Exception {
15691569
.assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueFuseable.ASYNC))
15701570
.assertFailureAndMessage(NullPointerException.class, "The combiner returned a null value");
15711571
}
1572+
1573+
@Test
1574+
@SuppressWarnings("unchecked")
1575+
public void syncFirstErrorsAfterItemDelayError() {
1576+
Flowable.combineLatestDelayError(Arrays.asList(
1577+
Flowable.just(21).concatWith(Flowable.<Integer>error(new TestException())),
1578+
Flowable.just(21).delay(100, TimeUnit.MILLISECONDS)
1579+
),
1580+
new Function<Object[], Object>() {
1581+
@Override
1582+
public Object apply(Object[] a) throws Exception {
1583+
return (Integer)a[0] + (Integer)a[1];
1584+
}
1585+
}
1586+
)
1587+
.test()
1588+
.awaitDone(5, TimeUnit.SECONDS)
1589+
.assertFailure(TestException.class, 42);
1590+
}
15721591
}

src/test/java/io/reactivex/internal/operators/observable/ObservableCombineLatestTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,4 +1198,23 @@ public Integer apply(Integer t1, Integer t2) throws Exception {
11981198
ps2.onNext(2);
11991199
ts.assertResult(3);
12001200
}
1201+
1202+
@Test
1203+
@SuppressWarnings("unchecked")
1204+
public void syncFirstErrorsAfterItemDelayError() {
1205+
Observable.combineLatestDelayError(Arrays.asList(
1206+
Observable.just(21).concatWith(Observable.<Integer>error(new TestException())),
1207+
Observable.just(21).delay(100, TimeUnit.MILLISECONDS)
1208+
),
1209+
new Function<Object[], Object>() {
1210+
@Override
1211+
public Object apply(Object[] a) throws Exception {
1212+
return (Integer)a[0] + (Integer)a[1];
1213+
}
1214+
}
1215+
)
1216+
.test()
1217+
.awaitDone(5, TimeUnit.SECONDS)
1218+
.assertFailure(TestException.class, 42);
1219+
}
12011220
}

0 commit comments

Comments
 (0)