Skip to content

Commit 053e506

Browse files
Merge pull request ReactiveX#1893 from akarnokd/MergeDelayErrorFix
Fixed incorrect error merging.
2 parents 4439cc1 + 752c2a7 commit 053e506

File tree

3 files changed

+80
-64
lines changed

3 files changed

+80
-64
lines changed

src/main/java/rx/internal/operators/OperatorMerge.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,9 @@ private boolean drainQueuesIfNeeded() {
307307
} finally {
308308
boolean moreToDrain = releaseEmitLock();
309309
// request outside of lock
310-
request(emitted);
310+
if (emitted > 0) {
311+
request(emitted);
312+
}
311313
if (!moreToDrain) {
312314
return true;
313315
}
@@ -397,11 +399,13 @@ public Boolean call(InnerSubscriber<T> s) {
397399

398400
@Override
399401
public void onError(Throwable e) {
400-
completed = true;
401-
innerError(e);
402+
if (!completed) {
403+
completed = true;
404+
innerError(e, true);
405+
}
402406
}
403407

404-
private void innerError(Throwable e) {
408+
private void innerError(Throwable e, boolean parent) {
405409
if (delayErrors) {
406410
synchronized (this) {
407411
if (exceptions == null) {
@@ -411,7 +415,9 @@ private void innerError(Throwable e) {
411415
exceptions.add(e);
412416
boolean sendOnComplete = false;
413417
synchronized (this) {
414-
wip--;
418+
if (!parent) {
419+
wip--;
420+
}
415421
if ((wip == 0 && completed) || (wip < 0)) {
416422
sendOnComplete = true;
417423
}
@@ -520,6 +526,7 @@ private static final class InnerSubscriber<T> extends Subscriber<T> {
520526
final MergeSubscriber<T> parentSubscriber;
521527
final MergeProducer<T> producer;
522528
/** Make sure the inner termination events are delivered only once. */
529+
@SuppressWarnings("unused")
523530
volatile int terminated;
524531
@SuppressWarnings("rawtypes")
525532
static final AtomicIntegerFieldUpdater<InnerSubscriber> ONCE_TERMINATED = AtomicIntegerFieldUpdater.newUpdater(InnerSubscriber.class, "terminated");
@@ -545,7 +552,7 @@ public void onNext(T t) {
545552
public void onError(Throwable e) {
546553
// it doesn't go through queues, it immediately onErrors and tears everything down
547554
if (ONCE_TERMINATED.compareAndSet(this, 0, 1)) {
548-
parentSubscriber.innerError(e);
555+
parentSubscriber.innerError(e, false);
549556
}
550557
}
551558

src/test/java/rx/internal/operators/OperatorMergeDelayErrorTest.java

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -496,26 +496,32 @@ public void testErrorInParentObservable() {
496496

497497
@Test
498498
public void testErrorInParentObservableDelayed() throws Exception {
499-
final TestASynchronous1sDelayedObservable o1 = new TestASynchronous1sDelayedObservable();
500-
final TestASynchronous1sDelayedObservable o2 = new TestASynchronous1sDelayedObservable();
501-
Observable<Observable<String>> parentObservable = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
502-
@Override
503-
public void call(Subscriber<? super Observable<String>> op) {
504-
op.onNext(Observable.create(o1));
505-
op.onNext(Observable.create(o2));
506-
op.onError(new NullPointerException("throwing exception in parent"));
507-
}
508-
});
509-
510-
TestSubscriber<String> ts = new TestSubscriber<String>(stringObserver);
511-
Observable<String> m = Observable.mergeDelayError(parentObservable);
512-
m.subscribe(ts);
513-
ts.awaitTerminalEvent(2000, TimeUnit.MILLISECONDS);
514-
ts.assertTerminalEvent();
515-
516-
verify(stringObserver, times(2)).onNext("hello");
517-
verify(stringObserver, times(1)).onError(any(NullPointerException.class));
518-
verify(stringObserver, never()).onCompleted();
499+
for (int i = 0; i < 50; i++) {
500+
final TestASynchronous1sDelayedObservable o1 = new TestASynchronous1sDelayedObservable();
501+
final TestASynchronous1sDelayedObservable o2 = new TestASynchronous1sDelayedObservable();
502+
Observable<Observable<String>> parentObservable = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
503+
@Override
504+
public void call(Subscriber<? super Observable<String>> op) {
505+
op.onNext(Observable.create(o1));
506+
op.onNext(Observable.create(o2));
507+
op.onError(new NullPointerException("throwing exception in parent"));
508+
}
509+
});
510+
511+
@SuppressWarnings("unchecked")
512+
Observer<String> stringObserver = mock(Observer.class);
513+
514+
TestSubscriber<String> ts = new TestSubscriber<String>(stringObserver);
515+
Observable<String> m = Observable.mergeDelayError(parentObservable);
516+
m.subscribe(ts);
517+
System.out.println("testErrorInParentObservableDelayed | " + i);
518+
ts.awaitTerminalEvent(2000, TimeUnit.MILLISECONDS);
519+
ts.assertTerminalEvent();
520+
521+
verify(stringObserver, times(2)).onNext("hello");
522+
verify(stringObserver, times(1)).onError(any(NullPointerException.class));
523+
verify(stringObserver, never()).onCompleted();
524+
}
519525
}
520526

521527
private static class TestASynchronous1sDelayedObservable implements Observable.OnSubscribe<String> {

src/test/java/rx/internal/operators/OperatorObserveOnTest.java

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -609,47 +609,50 @@ public void testAsyncChild() {
609609

610610
@Test
611611
public void testOnErrorCutsAheadOfOnNext() {
612-
final PublishSubject<Long> subject = PublishSubject.create();
613-
614-
final AtomicLong counter = new AtomicLong();
615-
TestSubscriber<Long> ts = new TestSubscriber<Long>(new Observer<Long>() {
616-
617-
@Override
618-
public void onCompleted() {
619-
620-
}
621-
622-
@Override
623-
public void onError(Throwable e) {
624-
625-
}
626-
627-
@Override
628-
public void onNext(Long t) {
629-
// simulate slow consumer to force backpressure failure
630-
try {
631-
Thread.sleep(1);
632-
} catch (InterruptedException e) {
612+
for (int i = 0; i < 50; i++) {
613+
final PublishSubject<Long> subject = PublishSubject.create();
614+
615+
final AtomicLong counter = new AtomicLong();
616+
TestSubscriber<Long> ts = new TestSubscriber<Long>(new Observer<Long>() {
617+
618+
@Override
619+
public void onCompleted() {
620+
621+
}
622+
623+
@Override
624+
public void onError(Throwable e) {
625+
626+
}
627+
628+
@Override
629+
public void onNext(Long t) {
630+
// simulate slow consumer to force backpressure failure
631+
try {
632+
Thread.sleep(1);
633+
} catch (InterruptedException e) {
634+
}
633635
}
636+
637+
});
638+
subject.observeOn(Schedulers.computation()).subscribe(ts);
639+
640+
// this will blow up with backpressure
641+
while (counter.get() < 102400) {
642+
subject.onNext(counter.get());
643+
counter.incrementAndGet();
634644
}
635-
636-
});
637-
subject.observeOn(Schedulers.computation()).subscribe(ts);
638-
639-
// this will blow up with backpressure
640-
while (counter.get() < 102400) {
641-
subject.onNext(counter.get());
642-
counter.incrementAndGet();
645+
646+
ts.awaitTerminalEvent();
647+
assertEquals(1, ts.getOnErrorEvents().size());
648+
assertTrue(ts.getOnErrorEvents().get(0) instanceof MissingBackpressureException);
649+
// assert that the values are sequential, that cutting in didn't allow skipping some but emitting others.
650+
// example [0, 1, 2] not [0, 1, 4]
651+
List<Long> onNextEvents = ts.getOnNextEvents();
652+
assertTrue(onNextEvents.isEmpty() || onNextEvents.size() == onNextEvents.get(onNextEvents.size() - 1) + 1);
653+
// we should emit the error without emitting the full buffer size
654+
assertTrue(onNextEvents.size() < RxRingBuffer.SIZE);
643655
}
644-
645-
ts.awaitTerminalEvent();
646-
assertEquals(1, ts.getOnErrorEvents().size());
647-
assertTrue(ts.getOnErrorEvents().get(0) instanceof MissingBackpressureException);
648-
// assert that the values are sequential, that cutting in didn't allow skipping some but emitting others.
649-
// example [0, 1, 2] not [0, 1, 4]
650-
assertTrue(ts.getOnNextEvents().size() == ts.getOnNextEvents().get(ts.getOnNextEvents().size() - 1) + 1);
651-
// we should emit the error without emitting the full buffer size
652-
assertTrue(ts.getOnNextEvents().size() < RxRingBuffer.SIZE);
653656
}
654657

655658
/**

0 commit comments

Comments
 (0)