|
3 | 3 | import static org.junit.Assert.*;
|
4 | 4 |
|
5 | 5 | import java.util.Iterator;
|
| 6 | +import java.util.concurrent.CountDownLatch; |
6 | 7 | import java.util.concurrent.Future;
|
| 8 | +import java.util.concurrent.atomic.AtomicReference; |
7 | 9 |
|
8 | 10 | import org.junit.Before;
|
9 | 11 | import org.junit.Test;
|
|
13 | 15 | import rx.Observable;
|
14 | 16 | import rx.Observer;
|
15 | 17 | import rx.Subscription;
|
| 18 | +import rx.operators.AtomicObservableSubscription; |
| 19 | +import rx.operators.AtomicObserver; |
16 | 20 | import rx.operators.OperationMostRecent;
|
17 | 21 | import rx.operators.OperationNext;
|
18 | 22 | import rx.operators.OperationToFuture;
|
19 | 23 | import rx.operators.OperationToIterator;
|
| 24 | +import rx.subscriptions.BooleanSubscription; |
20 | 25 | import rx.subscriptions.Subscriptions;
|
| 26 | +import rx.util.functions.Action1; |
21 | 27 | import rx.util.functions.Func1;
|
22 | 28 | import rx.util.functions.FuncN;
|
23 | 29 | import rx.util.functions.Functions;
|
@@ -309,6 +315,115 @@ protected BlockingObservable(Func1<Observer<T>, Subscription> onSubscribe) {
|
309 | 315 | super(onSubscribe);
|
310 | 316 | }
|
311 | 317 |
|
| 318 | + /** |
| 319 | + * Used for protecting against errors being thrown from Observer implementations and ensuring onNext/onError/onCompleted contract compliance. |
| 320 | + * <p> |
| 321 | + * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" |
| 322 | + */ |
| 323 | + private Subscription protectivelyWrapAndSubscribe(Observer<T> o) { |
| 324 | + AtomicObservableSubscription subscription = new AtomicObservableSubscription(); |
| 325 | + return subscription.wrap(subscribe(new AtomicObserver<T>(subscription, o))); |
| 326 | + } |
| 327 | + |
| 328 | + /** |
| 329 | + * Invokes an action for each element in the observable sequence, and blocks until the sequence is terminated. |
| 330 | + * <p> |
| 331 | + * NOTE: This will block even if the Observable is asynchronous. |
| 332 | + * <p> |
| 333 | + * This is similar to {@link #subscribe(Observer)} but blocks. Because it blocks it does not need the {@link Observer#onCompleted()} or {@link Observer#onError(Exception)} methods. |
| 334 | + * |
| 335 | + * @param onNext |
| 336 | + * {@link Action1} |
| 337 | + * @throws RuntimeException |
| 338 | + * if error occurs |
| 339 | + */ |
| 340 | + public void forEach(final Action1<T> onNext) { |
| 341 | + final CountDownLatch latch = new CountDownLatch(1); |
| 342 | + final AtomicReference<Exception> exceptionFromOnError = new AtomicReference<Exception>(); |
| 343 | + |
| 344 | + /** |
| 345 | + * Wrapping since raw functions provided by the user are being invoked. |
| 346 | + * |
| 347 | + * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" |
| 348 | + */ |
| 349 | + protectivelyWrapAndSubscribe(new Observer<T>() { |
| 350 | + @Override |
| 351 | + public void onCompleted() { |
| 352 | + latch.countDown(); |
| 353 | + } |
| 354 | + |
| 355 | + @Override |
| 356 | + public void onError(Exception e) { |
| 357 | + /* |
| 358 | + * If we receive an onError event we set the reference on the outer thread |
| 359 | + * so we can git it and throw after the latch.await(). |
| 360 | + * |
| 361 | + * We do this instead of throwing directly since this may be on a different thread and the latch is still waiting. |
| 362 | + */ |
| 363 | + exceptionFromOnError.set(e); |
| 364 | + latch.countDown(); |
| 365 | + } |
| 366 | + |
| 367 | + @Override |
| 368 | + public void onNext(T args) { |
| 369 | + onNext.call(args); |
| 370 | + } |
| 371 | + }); |
| 372 | + // block until the subscription completes and then return |
| 373 | + try { |
| 374 | + latch.await(); |
| 375 | + } catch (InterruptedException e) { |
| 376 | + // set the interrupted flag again so callers can still get it |
| 377 | + // for more information see https://github.com/Netflix/RxJava/pull/147#issuecomment-13624780 |
| 378 | + Thread.currentThread().interrupt(); |
| 379 | + // using Runtime so it is not checked |
| 380 | + throw new RuntimeException("Interrupted while waiting for subscription to complete.", e); |
| 381 | + } |
| 382 | + |
| 383 | + if (exceptionFromOnError.get() != null) { |
| 384 | + if (exceptionFromOnError.get() instanceof RuntimeException) { |
| 385 | + throw (RuntimeException) exceptionFromOnError.get(); |
| 386 | + } else { |
| 387 | + throw new RuntimeException(exceptionFromOnError.get()); |
| 388 | + } |
| 389 | + } |
| 390 | + } |
| 391 | + |
| 392 | + /** |
| 393 | + * Invokes an action for each element in the observable sequence, and blocks until the sequence is terminated. |
| 394 | + * <p> |
| 395 | + * NOTE: This will block even if the Observable is asynchronous. |
| 396 | + * <p> |
| 397 | + * This is similar to {@link #subscribe(Observer)} but blocks. Because it blocks it does not need the {@link Observer#onCompleted()} or {@link Observer#onError(Exception)} methods. |
| 398 | + * |
| 399 | + * @param o |
| 400 | + * onNext {@link Action1 action} |
| 401 | + * @throws RuntimeException |
| 402 | + * if error occurs |
| 403 | + */ |
| 404 | + @SuppressWarnings({ "rawtypes", "unchecked" }) |
| 405 | + public void forEach(final Object o) { |
| 406 | + if (o instanceof Action1) { |
| 407 | + // in case a dynamic language is not correctly handling the overloaded methods and we receive an Action1 just forward to the correct method. |
| 408 | + forEach((Action1) o); |
| 409 | + } |
| 410 | + |
| 411 | + // lookup and memoize onNext |
| 412 | + if (o == null) { |
| 413 | + throw new RuntimeException("onNext must be implemented"); |
| 414 | + } |
| 415 | + final FuncN onNext = Functions.from(o); |
| 416 | + |
| 417 | + forEach(new Action1() { |
| 418 | + |
| 419 | + @Override |
| 420 | + public void call(Object args) { |
| 421 | + onNext.call(args); |
| 422 | + } |
| 423 | + |
| 424 | + }); |
| 425 | + } |
| 426 | + |
312 | 427 | /**
|
313 | 428 | * Returns an iterator that iterates all values of the observable.
|
314 | 429 | *
|
@@ -731,6 +846,39 @@ public Subscription call(Observer<String> observer) {
|
731 | 846 | it.next();
|
732 | 847 |
|
733 | 848 | }
|
| 849 | + |
| 850 | + @Test |
| 851 | + public void testForEachWithError() { |
| 852 | + try { |
| 853 | + BlockingObservable.from(Observable.create(new Func1<Observer<String>, Subscription>() { |
| 854 | + |
| 855 | + @Override |
| 856 | + public Subscription call(final Observer<String> observer) { |
| 857 | + final BooleanSubscription subscription = new BooleanSubscription(); |
| 858 | + new Thread(new Runnable() { |
| 859 | + |
| 860 | + @Override |
| 861 | + public void run() { |
| 862 | + observer.onNext("one"); |
| 863 | + observer.onNext("two"); |
| 864 | + observer.onNext("three"); |
| 865 | + observer.onCompleted(); |
| 866 | + } |
| 867 | + }).start(); |
| 868 | + return subscription; |
| 869 | + } |
| 870 | + })).forEach(new Action1<String>() { |
| 871 | + |
| 872 | + @Override |
| 873 | + public void call(String t1) { |
| 874 | + throw new RuntimeException("fail"); |
| 875 | + } |
| 876 | + }); |
| 877 | + fail("we expect an exception to be thrown"); |
| 878 | + } catch (Exception e) { |
| 879 | + // do nothing as we expect this |
| 880 | + } |
| 881 | + } |
734 | 882 |
|
735 | 883 | private static class TestException extends RuntimeException {
|
736 | 884 | private static final long serialVersionUID = 1L;
|
|
0 commit comments