Skip to content

Commit 7020bcc

Browse files
author
Roman Mazur
committed
Unsubscribe when thread is interrupted
1 parent c49704b commit 7020bcc

File tree

5 files changed

+142
-3
lines changed

5 files changed

+142
-3
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public boolean hasNext() {
9696
try {
9797
notify.acquire();
9898
} catch (InterruptedException ex) {
99+
unsubscribe();
99100
Thread.currentThread().interrupt();
100101
iNotif = Notification.createOnError(ex);
101102
throw Exceptions.propagate(ex);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ private boolean moveToNext() {
117117
}
118118
throw new IllegalStateException("Should not reach here");
119119
} catch (InterruptedException e) {
120+
observer.unsubscribe();
120121
Thread.currentThread().interrupt();
121122
error = e;
122123
throw Exceptions.propagate(error);

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import rx.Notification;
2424
import rx.Observable;
2525
import rx.Subscriber;
26+
import rx.Subscription;
2627
import rx.exceptions.Exceptions;
2728

2829
/**
@@ -49,7 +50,7 @@ public static <T> Iterator<T> toIterator(Observable<? extends T> source) {
4950
final BlockingQueue<Notification<? extends T>> notifications = new LinkedBlockingQueue<Notification<? extends T>>();
5051

5152
// using subscribe instead of unsafeSubscribe since this is a BlockingObservable "final subscribe"
52-
source.materialize().subscribe(new Subscriber<Notification<? extends T>>() {
53+
final Subscription subscription = source.materialize().subscribe(new Subscriber<Notification<? extends T>>() {
5354
@Override
5455
public void onCompleted() {
5556
// ignore
@@ -94,6 +95,7 @@ private Notification<? extends T> take() {
9495
try {
9596
return notifications.take();
9697
} catch (InterruptedException e) {
98+
subscription.unsubscribe();
9799
throw Exceptions.propagate(e);
98100
}
99101
}

src/main/java/rx/observables/BlockingObservable.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import rx.Observable;
2525
import rx.Subscriber;
26+
import rx.Subscription;
2627
import rx.functions.Action1;
2728
import rx.functions.Func1;
2829
import rx.functions.Functions;
@@ -98,7 +99,7 @@ public void forEach(final Action1<? super T> onNext) {
9899
* Use 'subscribe' instead of 'unsafeSubscribe' for Rx contract behavior
99100
* as this is the final subscribe in the chain.
100101
*/
101-
o.subscribe(new Subscriber<T>() {
102+
Subscription subscription = o.subscribe(new Subscriber<T>() {
102103
@Override
103104
public void onCompleted() {
104105
latch.countDown();
@@ -127,6 +128,7 @@ public void onNext(T args) {
127128
try {
128129
latch.await();
129130
} catch (InterruptedException e) {
131+
subscription.unsubscribe();
130132
// set the interrupted flag again so callers can still get it
131133
// for more information see https://github.com/ReactiveX/RxJava/pull/147#issuecomment-13624780
132134
Thread.currentThread().interrupt();
@@ -438,7 +440,7 @@ private T blockForSingle(final Observable<? extends T> observable) {
438440
final AtomicReference<Throwable> returnException = new AtomicReference<Throwable>();
439441
final CountDownLatch latch = new CountDownLatch(1);
440442

441-
observable.subscribe(new Subscriber<T>() {
443+
Subscription subscription = observable.subscribe(new Subscriber<T>() {
442444
@Override
443445
public void onCompleted() {
444446
latch.countDown();
@@ -459,6 +461,7 @@ public void onNext(final T item) {
459461
try {
460462
latch.await();
461463
} catch (InterruptedException e) {
464+
subscription.unsubscribe();
462465
Thread.currentThread().interrupt();
463466
throw new RuntimeException("Interrupted while waiting for subscription to complete.", e);
464467
}

src/test/java/rx/observables/BlockingObservableTest.java

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,4 +410,136 @@ public void call() {
410410
}
411411
assertTrue("Timeout means `unsubscribe` is not called", unsubscribe.await(30, TimeUnit.SECONDS));
412412
}
413+
414+
@Test
415+
public void testUnsubscribeFromSingleWhenInterrupted() throws InterruptedException {
416+
new InterruptionTests().assertUnsubscribeIsInvoked("single()", new Action1<BlockingObservable<Void>>() {
417+
@Override
418+
public void call(final BlockingObservable<Void> o) {
419+
o.single();
420+
}
421+
});
422+
}
423+
424+
@Test
425+
public void testUnsubscribeFromForEachWhenInterrupted() throws InterruptedException {
426+
new InterruptionTests().assertUnsubscribeIsInvoked("forEach()", new Action1<BlockingObservable<Void>>() {
427+
@Override
428+
public void call(final BlockingObservable<Void> o) {
429+
o.forEach(new Action1<Void>() {
430+
@Override
431+
public void call(final Void aVoid) {
432+
// nothing
433+
}
434+
});
435+
}
436+
});
437+
}
438+
439+
@Test
440+
public void testUnsubscribeFromFirstWhenInterrupted() throws InterruptedException {
441+
new InterruptionTests().assertUnsubscribeIsInvoked("first()", new Action1<BlockingObservable<Void>>() {
442+
@Override
443+
public void call(final BlockingObservable<Void> o) {
444+
o.first();
445+
}
446+
});
447+
}
448+
449+
@Test
450+
public void testUnsubscribeFromLastWhenInterrupted() throws InterruptedException {
451+
new InterruptionTests().assertUnsubscribeIsInvoked("last()", new Action1<BlockingObservable<Void>>() {
452+
@Override
453+
public void call(final BlockingObservable<Void> o) {
454+
o.last();
455+
}
456+
});
457+
}
458+
459+
@Test
460+
public void testUnsubscribeFromLatestWhenInterrupted() throws InterruptedException {
461+
new InterruptionTests().assertUnsubscribeIsInvoked("latest()", new Action1<BlockingObservable<Void>>() {
462+
@Override
463+
public void call(final BlockingObservable<Void> o) {
464+
o.latest().iterator().next();
465+
}
466+
});
467+
}
468+
469+
@Test
470+
public void testUnsubscribeFromNextWhenInterrupted() throws InterruptedException {
471+
new InterruptionTests().assertUnsubscribeIsInvoked("next()", new Action1<BlockingObservable<Void>>() {
472+
@Override
473+
public void call(final BlockingObservable<Void> o) {
474+
o.next().iterator().next();
475+
}
476+
});
477+
}
478+
479+
@Test
480+
public void testUnsubscribeFromGetIteratorWhenInterrupted() throws InterruptedException {
481+
new InterruptionTests().assertUnsubscribeIsInvoked("getIterator()", new Action1<BlockingObservable<Void>>() {
482+
@Override
483+
public void call(final BlockingObservable<Void> o) {
484+
o.getIterator().next();
485+
}
486+
});
487+
}
488+
489+
@Test
490+
public void testUnsubscribeFromToIterableWhenInterrupted() throws InterruptedException {
491+
new InterruptionTests().assertUnsubscribeIsInvoked("toIterable()", new Action1<BlockingObservable<Void>>() {
492+
@Override
493+
public void call(final BlockingObservable<Void> o) {
494+
o.toIterable().iterator().next();
495+
}
496+
});
497+
}
498+
499+
/** Utilities set for interruption behaviour tests. */
500+
private static class InterruptionTests {
501+
502+
private boolean isUnSubscribed;
503+
private RuntimeException error;
504+
private CountDownLatch latch = new CountDownLatch(1);
505+
506+
private Observable<Void> createObservable() {
507+
return Observable.<Void>never().doOnUnsubscribe(new Action0() {
508+
@Override
509+
public void call() {
510+
isUnSubscribed = true;
511+
}
512+
});
513+
}
514+
515+
private void startBlockingAndInterrupt(final Action1<BlockingObservable<Void>> blockingAction) {
516+
Thread subscriptionThread = new Thread() {
517+
@Override
518+
public void run() {
519+
try {
520+
blockingAction.call(createObservable().toBlocking());
521+
} catch (RuntimeException e) {
522+
if (!(e.getCause() instanceof InterruptedException)) {
523+
error = e;
524+
}
525+
}
526+
latch.countDown();
527+
}
528+
};
529+
subscriptionThread.start();
530+
subscriptionThread.interrupt();
531+
}
532+
533+
void assertUnsubscribeIsInvoked(final String method, final Action1<BlockingObservable<Void>> blockingAction)
534+
throws InterruptedException {
535+
startBlockingAndInterrupt(blockingAction);
536+
assertTrue("Timeout means interruption is not performed", latch.await(30, TimeUnit.SECONDS));
537+
if (error != null) {
538+
throw error;
539+
}
540+
assertTrue("'unsubscribe' is not invoked when thread is interrupted for " + method, isUnSubscribed);
541+
}
542+
543+
}
544+
413545
}

0 commit comments

Comments
 (0)