Skip to content

Commit ac3cee8

Browse files
Protect subscribe/forEach implementations against user provided function failures
Related to ReactiveX#216 The new forEach unit test went into a deadlock prior to this fix.
1 parent c9fc4df commit ac3cee8

File tree

1 file changed

+110
-9
lines changed

1 file changed

+110
-9
lines changed

rxjava-core/src/main/java/rx/Observable.java

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
import rx.util.functions.Func3;
8282
import rx.util.functions.Func4;
8383
import rx.util.functions.FuncN;
84+
import rx.util.functions.Function;
8485
import rx.util.functions.FunctionLanguageAdaptor;
8586
import rx.util.functions.Functions;
8687

@@ -152,7 +153,7 @@ public Subscription subscribe(Observer<T> observer) {
152153
/**
153154
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
154155
*/
155-
if (observer.getClass().getPackage().getName().startsWith("rx")) {
156+
if (isInternalImplementation(observer)) {
156157
Subscription s = onSubscribe.call(observer);
157158
if (s == null) {
158159
// this generally shouldn't be the case on a 'trusted' onSubscribe but in case it happens
@@ -178,6 +179,16 @@ public Subscription subscribe(Observer<T> observer) {
178179
}
179180
}
180181

182+
/**
183+
* Used for protecting against errors being thrown from Observer implementations and ensuring onNext/onError/onCompleted contract compliance.
184+
* <p>
185+
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
186+
*/
187+
private Subscription protectivelyWrapAndSubscribe(Observer<T> o) {
188+
AtomicObservableSubscription subscription = new AtomicObservableSubscription();
189+
return subscription.wrap(subscribe(new AtomicObserver<T>(subscription, o)));
190+
}
191+
181192
@SuppressWarnings({ "rawtypes", "unchecked" })
182193
public Subscription subscribe(final Map<String, Object> callbacks) {
183194
// lookup and memoize onNext
@@ -187,7 +198,12 @@ public Subscription subscribe(final Map<String, Object> callbacks) {
187198
}
188199
final FuncN onNext = Functions.from(_onNext);
189200

190-
return subscribe(new Observer() {
201+
/**
202+
* Wrapping since raw functions provided by the user are being invoked.
203+
*
204+
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
205+
*/
206+
return protectivelyWrapAndSubscribe(new Observer() {
191207

192208
public void onCompleted() {
193209
Object onComplete = callbacks.get("onCompleted");
@@ -224,7 +240,12 @@ public Subscription subscribe(final Object o) {
224240
}
225241
final FuncN onNext = Functions.from(o);
226242

227-
return subscribe(new Observer() {
243+
/**
244+
* Wrapping since raw functions provided by the user are being invoked.
245+
*
246+
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
247+
*/
248+
return protectivelyWrapAndSubscribe(new Observer() {
228249

229250
public void onCompleted() {
230251
// do nothing
@@ -244,7 +265,12 @@ public void onNext(Object args) {
244265

245266
public Subscription subscribe(final Action1<T> onNext) {
246267

247-
return subscribe(new Observer<T>() {
268+
/**
269+
* Wrapping since raw functions provided by the user are being invoked.
270+
*
271+
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
272+
*/
273+
return protectivelyWrapAndSubscribe(new Observer<T>() {
248274

249275
public void onCompleted() {
250276
// do nothing
@@ -273,7 +299,12 @@ public Subscription subscribe(final Object onNext, final Object onError) {
273299
}
274300
final FuncN onNextFunction = Functions.from(onNext);
275301

276-
return subscribe(new Observer() {
302+
/**
303+
* Wrapping since raw functions provided by the user are being invoked.
304+
*
305+
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
306+
*/
307+
return protectivelyWrapAndSubscribe(new Observer() {
277308

278309
public void onCompleted() {
279310
// do nothing
@@ -295,7 +326,12 @@ public void onNext(Object args) {
295326

296327
public Subscription subscribe(final Action1<T> onNext, final Action1<Exception> onError) {
297328

298-
return subscribe(new Observer<T>() {
329+
/**
330+
* Wrapping since raw functions provided by the user are being invoked.
331+
*
332+
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
333+
*/
334+
return protectivelyWrapAndSubscribe(new Observer<T>() {
299335

300336
public void onCompleted() {
301337
// do nothing
@@ -326,7 +362,12 @@ public Subscription subscribe(final Object onNext, final Object onError, final O
326362
}
327363
final FuncN onNextFunction = Functions.from(onNext);
328364

329-
return subscribe(new Observer() {
365+
/**
366+
* Wrapping since raw functions provided by the user are being invoked.
367+
*
368+
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
369+
*/
370+
return protectivelyWrapAndSubscribe(new Observer() {
330371

331372
public void onCompleted() {
332373
if (onComplete != null) {
@@ -350,7 +391,12 @@ public void onNext(Object args) {
350391

351392
public Subscription subscribe(final Action1<T> onNext, final Action1<Exception> onError, final Action0 onComplete) {
352393

353-
return subscribe(new Observer<T>() {
394+
/**
395+
* Wrapping since raw functions provided by the user are being invoked.
396+
*
397+
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
398+
*/
399+
return protectivelyWrapAndSubscribe(new Observer<T>() {
354400

355401
public void onCompleted() {
356402
onComplete.call();
@@ -389,7 +435,12 @@ public void forEach(final Action1<T> onNext) {
389435
final CountDownLatch latch = new CountDownLatch(1);
390436
final AtomicReference<Exception> exceptionFromOnError = new AtomicReference<Exception>();
391437

392-
subscribe(new Observer<T>() {
438+
/**
439+
* Wrapping since raw functions provided by the user are being invoked.
440+
*
441+
* See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
442+
*/
443+
protectivelyWrapAndSubscribe(new Observer<T>() {
393444
public void onCompleted() {
394445
latch.countDown();
395446
}
@@ -3260,6 +3311,23 @@ public Iterable<T> mostRecent(T initialValue) {
32603311
return mostRecent(this, initialValue);
32613312
}
32623313

3314+
/**
3315+
* Whether a given {@link Function} is an internal implementation inside rx.* packages or not.
3316+
* <p>
3317+
* For why this is being used see https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator"
3318+
*
3319+
* NOTE: If strong reasons for not depending on package names comes up then the implementation of this method can change to looking for a marker interface.
3320+
*
3321+
* @param f
3322+
* @return
3323+
*/
3324+
private boolean isInternalImplementation(Object o) {
3325+
if (o == null) {
3326+
return true;
3327+
}
3328+
return (o.getClass().getPackage().getName().startsWith("rx."));
3329+
}
3330+
32633331
public static class UnitTest {
32643332

32653333
@Mock
@@ -3723,6 +3791,39 @@ public void onNext(String v) {
37233791
}
37243792
}
37253793

3794+
@Test
3795+
public void testForEachWithError() {
3796+
try {
3797+
Observable.create(new Func1<Observer<String>, Subscription>() {
3798+
3799+
@Override
3800+
public Subscription call(final Observer<String> observer) {
3801+
final BooleanSubscription subscription = new BooleanSubscription();
3802+
new Thread(new Runnable() {
3803+
3804+
@Override
3805+
public void run() {
3806+
observer.onNext("one");
3807+
observer.onNext("two");
3808+
observer.onNext("three");
3809+
observer.onCompleted();
3810+
}
3811+
}).start();
3812+
return subscription;
3813+
}
3814+
}).forEach(new Action1<String>() {
3815+
3816+
@Override
3817+
public void call(String t1) {
3818+
throw new RuntimeException("fail");
3819+
}
3820+
});
3821+
fail("we expect an exception to be thrown");
3822+
} catch (Exception e) {
3823+
// do nothing as we expect this
3824+
}
3825+
}
3826+
37263827
private static class TestException extends RuntimeException {
37273828
private static final long serialVersionUID = 1L;
37283829
}

0 commit comments

Comments
 (0)