diff --git a/.travis.yml b/.travis.yml index f07cf8d5e4..e9ae55c471 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: java jdk: -- oraclejdk7 -sudo: false +- oraclejdk8 +sudo: required # as per http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ git: diff --git a/CHANGES.md b/CHANGES.md index f0558e3d6f..31299c26f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,200 @@ # RxJava Releases # +### Version 1.3.8 - March 31, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.8%7C)) + +RxJava 1.x is now officially **end-of-life (EOL)**. No further developments, bugfixes, enhancements, javadoc changes, maintenance will be provided by this project after version **1.3.8**. + +Users are encourage to [migrate to 2.x](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0). In accordance, the wiki will be updated in the coming months to describe 2.x features and properties. + +#### Bugfixes + +- [Pull 5935](https://github.com/ReactiveX/RxJava/pull/5935): Fix `take()` to route late errors to `RxJavaHooks`. + +### Version 1.3.7 - March 21, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.7%7C)) + +#### Bugfixes + +- [Pull 5917](https://github.com/ReactiveX/RxJava/pull/5917): Fix and deprecate evicting `groupBy` and add a new overload with the corrected signature. + +### Version 1.3.6 - February 15, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.6%7C)) + +#### Bugfixes + +- [Pull 5850](https://github.com/ReactiveX/RxJava/pull/5850): Fix a race condition that may make `OperatorMaterialize` emit the wrong signals. +- [Pull 5851](https://github.com/ReactiveX/RxJava/pull/5851): Fix a race condition in `OperatorMerge.InnerSubscriber#onError` causing incorrect terminal event. + +### Version 1.3.5 - January 27, 2018 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.5%7C)) + +#### Other + +- [Pull 5820](https://github.com/ReactiveX/RxJava/pull/5820): `RxJavaPlugins` lookup workaround for `System.getProperties()` access restrictions. + +### Version 1.3.4 - November 19, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.4%7C)) + +#### Bugfixes + +- [Pull 5696](https://github.com/ReactiveX/RxJava/pull/5696): Fix `Completable.concat` to use `replace` and don't dispose the old `Subscription` on switching to the next source. +- [Pull 5726](https://github.com/ReactiveX/RxJava/pull/5726): Fix broken backpressure through `unsubscribeOn()`. + +#### Documentation + +- [Pull 5719](https://github.com/ReactiveX/RxJava/pull/5719): Document the timed `take()` operator will signal the `onCompleted` event on the given `Scheduler` when it times out. + +### Version 1.3.3 - October 19, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.3%7C)) + +#### Bugfixes + +- [Pull 5660](https://github.com/ReactiveX/RxJava/pull/5660): Fix `timeout` (timed, selector) unsubscribe bug. + +### Version 1.3.2 - September 15, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.2%7C)) + +#### Bugfixes + +- [Pull 5602](https://github.com/ReactiveX/RxJava/pull/5602): Workaround for CHM.keySet bad type with Java 8 compiler + +### Version 1.3.1 - September 10, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.1%7C)) + +#### API changes +*Remark: submitted & merged before the feature freeze of June 1st.* + +- [Pull 5332](https://github.com/ReactiveX/RxJava/pull/5332): Add the `cast` operator to `Single`. + +#### Bugfixes + +- [Pull 5430](https://github.com/ReactiveX/RxJava/pull/5430): Fix premature cleanup in `AsyncOnSubscribe` when the last `Observable` is still running. +- [Pull 5437](https://github.com/ReactiveX/RxJava/pull/5437): `TestSubscriber::assertValuesAndClear` should reset `valueCount`. +- [Pull 5470](https://github.com/ReactiveX/RxJava/pull/5470): Fix eager call to `RxJavHooks.onError` in `SafeCompletableSuscriber`. + +### Version 1.3.0 - May 5, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.3.0%7C)) + +#### Summary + +Version 1.3.0 is the next minor release of the 1.x series of RxJava containing many formerly experimental API components promoted to standard. Most notably the `Completable` base reactive type is now standard as well. + +Note that the experimental `rx.Observable.fromEmitter()` has been removed in favor for the now also standard `Observable.create(Action1> emitter, Emitter.BackpressureMode backpressure)` + +The planned lifecycle of the 1.x line is as follows: + +Date | Remark +------------|------------------- + **June 1, 2017** | Feature freeze, only bugfixes from this on. + **September 1, 2017** | Release `1.4.0` finalizing the remaining API components. + **March 31, 2018** | End of development. + +The following components have been promoted to standard: + +**Classes, interfaces** + +- **classes**: `AssemblyStackTraceException`, `RxJavaCompletableExecutionHook`, `RxJavaHooks`, `UnicastSubject`, `BlockingSingle`, `Completable`, `AssertableSubscriber`, `AsyncCompletableSubscriber`, `SafeCompletableSubscriber` +- **interfaces**: `Cancellable`, `Emitter`, `SingleEmitter`, `CompletableEmitter`, `CompletableSubscriber`, `BackpressureOverflow.Strategy` + +**Operators** + +- **`Observable`**: `create`, `unsafeCreate`, `to`, `zip(Observable[], FuncN)`, `flatMapCompletable`, `flatMapSingle`, `groupby(Func1, Func1, Func1, Map>)`, `onTerminateDetach`, `rebatchRequests`, `subscribeOn(Scheduler, boolean)`, `sorted`, `withLatestFrom`, `test`, `toCompletable`, `concatDelayError`, `mergeDelayError`, `switchOnNextDelayError`, `using(Func0, Func1, Action1, boolean)`, `concatMapDelayError`, `delaySubscription(Observable)`, `distinctUntilChanged(Func2)`, `concatEager`, `concatMapEager`, `onBackpressureBuffer(long, Action0, BackpressureOverflow.Strategy)`, `switchMapDelayError`, `toSortedList(int)`, `toSortedList(Func2, int)` +- **`Completable`**: `fromEmitter`, `test` +- **`Single`**: `fromEmitter`, `merge`, `mergeDelayError`, `cache`, `to`, `doOnEach`, `doOnSuccess`, `test`, `onErrorResumeNext`, `toCompletable`, `doOnError`, `doOnSubscribe`, `delay`, `defer`, `doOnUnsubscribe`, `doAfterTerminate`, `flatMapCompletable`, `lift`, `toBlocking`, `using`, `delaySubscription(Observable)` +- **`TestSubscriber`**: `getCompletions`, `awaitValueCount`, `assertValuesAndClear` +- **`SyncOnSubscriber`**: `createSingleState`, `createStateful`, `createStateless` + +**Other** + +- `Schedulers.reset` +- `CompositeException(Throwable...)` constructor +- `Exceptions.throwOrReport` (4 overloads) +- `BlockingObservable.subscribe` (6 overloads) +- **`RxJavaSchedulersHook`**: `createComputationScheduler`, `createIoScheduler`, `createNewThreadScheduler` +- **internal**: `AssertableSubscriberObservable`, `FlatMapCompletable`, `FlatMapSingle`, `SchedulerWhen`, `BackpressureDrainManager`, `BlockingUtils`. +- **`RxJavaPlugins`**: `reset`, `getCompletableExecutionHook`, `registerCompletableExecutionHook` +- **`RxJavaErrorHandler`**: `handleOnNextValueRendering`, `render` + +In addition, the class `AsyncOnsubscribe` with its 7 factory methods and `Observable.create(AsyncOnSubscribe)` have been promoted to **beta**. + +#### Acknowledgements + +Thanks to all who contributed to the 1.x line in the past 6 months (in order they appear on the [commit](https://github.com/ReactiveX/RxJava/commits/1.x) page): + +@mtiidla, @dhendry, @mostroverkhov, @yshrsmz, @BraisGabin, @cesar1000, @Jawnnypoo, @chanx2, @abersnaze, @davidmoten, @ortex, @marwinxxii, @ekchang, @pyricau, @JakeWharton, @VictorAlbertos + + +### Version 1.2.10 - April 26, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.10%7C)) + +#### Bugfixes + +- [Pull 5225](https://github.com/ReactiveX/RxJava/pull/5225): Fix `Completable.onErrorResumeNext` unsubscribe not propagated. + +#### Other + +- [Pull 5250](https://github.com/ReactiveX/RxJava/pull/5250): Defer creation of the `TimeoutException` when using the `Single.timeout()` operator. +- [Pull 5258](https://github.com/ReactiveX/RxJava/pull/5258): Use IntelliJ IDE friendly assertion failure message. + +### Version 1.2.9 - March 24, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.9%7C)) + +#### API enhancements +- [Pull 5146](https://github.com/ReactiveX/RxJava/pull/5146): Add `Single.unsubscribeOn`. +- [Pull 5195](https://github.com/ReactiveX/RxJava/pull/5195): Enhance `UnicastSubject` with optional delay error behavior. + +#### Bugfixes + +- [Pull 5141](https://github.com/ReactiveX/RxJava/pull/5141): Fix timed `replay()` not terminating when all items timeout. +- [Pull 5181](https://github.com/ReactiveX/RxJava/pull/5181): `replay().refCount()` avoid leaking items between connections. + +### Version 1.2.7 - February 24, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.7%7C)) + +#### Deprecation of `create(OnSubscribe)` + +The method started out in RxJava 0.x as a simple and direct way for implementing custom operators because 0.x had a much simpler protocol requirements. Over the years, as the `Observable` protocol evolved and `create` became a powerful and complicated extension point of RxJava that required users to implement the `Observable` protocol, including cancellation and backpressure manually. + +Unfortunately, guides, blogs, StackOverflow answers and mere typical user behavior still leads to this `create` method and lots of confusion, unstoppable sequences and `MissingBackpressureException`. We tried remedying the situation by introducing `fromEmitter` with limited discoverability success. + +**Therefore, as of 1.2.7 the `create()` method is now deprecated** (but won't be removed due to binary compatibility requirements). In addition `fromEmitter` has been deprecate-renamed to `create(Action1, BackpressureMode)` and the experimental-marked `fromEmitter` itself will be removed in 1.3.0. + +Since the functionality of `create()` was useful for writing (custom) operators inside and outside of RxJava, the new `unsafeCreate(OnSubscribe)` method was introduced as the replacement. + +The new `create()` and `unsafeCreate()` methods will be fast-tracked to become standard in 1.3.0. + +#### API enhancements + +- [Pull 5086](https://github.com/ReactiveX/RxJava/pull/5086): Deprecate `create()`, add alternatives +- [Pull 5092](https://github.com/ReactiveX/RxJava/pull/5092): Add `Single.merge(Observable>)`, `Observable.flatMapSingle()` & `Observable.flatMapCompletable`. +- [Pull 5091](https://github.com/ReactiveX/RxJava/pull/5091): Add `subscribeOn(Scheduler, boolean)` avoid same-pool deadlock. + +#### API deprecations + +- [Pull 5086](https://github.com/ReactiveX/RxJava/pull/5086): + - Deprecate `Observable.create(OnSubscribe)`, + - Deprecate `fromEmitter` in favor of `Observable.create(Action1, BackpressureMode)`. + +#### Bugfixes + +- [Pull 5091](https://github.com/ReactiveX/RxJava/pull/5091): `create(Action1, BackpressureMode)`+`subscribeOn` avoid same-pool deadlock. +- [Pull 5123](https://github.com/ReactiveX/RxJava/pull/5123): `throttleFirst` detecting clock-drift backwards to open the gate + +#### Other + +- [Pull 5125](https://github.com/ReactiveX/RxJava/pull/5125): reduce stack depth with `switchIfEmpty` + +### Version 1.2.6 - February 3, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.6%7C)) + +#### Documentation + +- [Pull 5000](https://github.com/ReactiveX/RxJava/pull/5000): Add which are the other stardard methods of create +- [Pull 5007](https://github.com/ReactiveX/RxJava/pull/5007): update `sample(time)` diagram to indicate emission of last +- [Pull 5048](https://github.com/ReactiveX/RxJava/pull/5048): Improve the javadoc of `BehaviorSubject` + +#### Bugfixes + +- [Pull 5030](https://github.com/ReactiveX/RxJava/pull/5030): fix `groupBy` consuming the upstream in an unbounded manner + +### Version 1.2.5 - January 6, 2017 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.5%7C)) + +#### Documentation + +- [Pull 4963](https://github.com/ReactiveX/RxJava/pull/4963): Add missing marbles, fix image sizes + +#### Bugfixes + +- [Pull 4941](https://github.com/ReactiveX/RxJava/pull/4941): Fix `Completable.mergeDelayError` to check unsafe availability + ### Version 1.2.4 - December 15, 2016 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex%7Crxjava%7C1.2.4%7C)) #### Other diff --git a/README.md b/README.md index 3525242ea4..a0eabf5886 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # RxJava: Reactive Extensions for the JVM +## End-of-Life notice + +As of March 31, 2018, The RxJava 1.x branch and version is end-of-life (EOL). No further development, bugfixes, documentation changes, PRs, releases or maintenance will be performed by the project on the 1.x line. + +Users are encouraged to migrate to [3.x](https://github.com/ReactiveX/RxJava) which is currently the only official RxJava version being managed. + +---------------------------------- + [![codecov.io](http://codecov.io/github/ReactiveX/RxJava/coverage.svg?branch=1.x)](http://codecov.io/github/ReactiveX/RxJava?branch=1.x) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.reactivex/rxjava/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.reactivex/rxjava) diff --git a/build.gradle b/build.gradle index 570ceb242f..8d0d2fa087 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ dependencies { testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' - testCompile 'com.google.guava:guava:19.0' + testCompile 'com.google.guava:guava:24.0-jre' testCompile 'com.pushtorefresh.java-private-constructor-checker:checker:1.2.0' perfCompile 'org.openjdk.jmh:jmh-core:1.11.3' diff --git a/src/main/java/rx/BackpressureOverflow.java b/src/main/java/rx/BackpressureOverflow.java index 39948d8804..6603a6d220 100644 --- a/src/main/java/rx/BackpressureOverflow.java +++ b/src/main/java/rx/BackpressureOverflow.java @@ -15,15 +15,13 @@ */ package rx; -import rx.annotations.Beta; import rx.exceptions.MissingBackpressureException; /** * Generic strategy and default implementations to deal with backpressure buffer overflows. * - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Beta public final class BackpressureOverflow { private BackpressureOverflow() { diff --git a/src/main/java/rx/Completable.java b/src/main/java/rx/Completable.java index e9b78c569f..1ecc1e1993 100644 --- a/src/main/java/rx/Completable.java +++ b/src/main/java/rx/Completable.java @@ -20,7 +20,6 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; -import rx.annotations.*; import rx.exceptions.*; import rx.functions.*; import rx.internal.observers.AssertableSubscriberObservable; @@ -35,10 +34,9 @@ * Represents a deferred computation without any value but only indication for completion or exception. * * The class follows a similar event pattern as Reactive-Streams: onSubscribe (onError|onComplete)? - * - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * + * @since 1.3 */ -@Beta public class Completable { /** The actual subscription action. */ private final OnSubscribe onSubscribe; @@ -539,9 +537,8 @@ public void call(rx.CompletableSubscriber s) { * Completable's protocol are held. * @param producer the callback invoked for each incoming CompletableSubscriber * @return the new Completable instance - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public static Completable fromEmitter(Action1 producer) { return create(new CompletableFromEmitter(producer)); } @@ -1729,6 +1726,7 @@ public final Completable onErrorResumeNext(final Func1 R to(Func1 converter) { * @return the new Observable created */ public final Observable toObservable() { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { unsafeSubscribe(s); @@ -2384,10 +2382,10 @@ public void call() { *
Scheduler:
*
{@code test} does not operate by default on a particular {@link Scheduler}.
* + *

History: 1.2.3 - experimental * @return the new AssertableSubscriber instance - * @since 1.2.3 + * @since 1.3 */ - @Experimental public final AssertableSubscriber test() { AssertableSubscriberObservable ts = AssertableSubscriberObservable.create(Long.MAX_VALUE); subscribe(ts); diff --git a/src/main/java/rx/CompletableEmitter.java b/src/main/java/rx/CompletableEmitter.java index 09f51e0262..dc5f83efaa 100644 --- a/src/main/java/rx/CompletableEmitter.java +++ b/src/main/java/rx/CompletableEmitter.java @@ -15,7 +15,6 @@ */ package rx; -import rx.annotations.Experimental; import rx.functions.Cancellable; /** @@ -24,9 +23,8 @@ *

* All methods are thread-safe; calling onCompleted or onError twice or one after the other has * no effect. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Experimental public interface CompletableEmitter { /** diff --git a/src/main/java/rx/CompletableSubscriber.java b/src/main/java/rx/CompletableSubscriber.java index 2aef783769..054a97474e 100644 --- a/src/main/java/rx/CompletableSubscriber.java +++ b/src/main/java/rx/CompletableSubscriber.java @@ -15,12 +15,10 @@ */ package rx; -import rx.annotations.Experimental; - /** * Represents the subscription API callbacks when subscribing to a Completable instance. + * @since 1.3 */ -@Experimental public interface CompletableSubscriber { /** * Called once the deferred computation completes normally. diff --git a/src/main/java/rx/Emitter.java b/src/main/java/rx/Emitter.java index 5ed8697b71..3a81d7f01e 100644 --- a/src/main/java/rx/Emitter.java +++ b/src/main/java/rx/Emitter.java @@ -16,7 +16,6 @@ package rx; -import rx.annotations.Experimental; import rx.functions.Cancellable; /** @@ -29,9 +28,8 @@ * other methods are thread-safe. * * @param the value type to emit - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Experimental public interface Emitter extends Observer { /** diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java index 434cbd22e8..99e84ec609 100644 --- a/src/main/java/rx/Observable.java +++ b/src/main/java/rx/Observable.java @@ -51,7 +51,7 @@ public class Observable { /** * Creates an Observable with a Function to execute when it is subscribed to. *

- * Note: Use {@link #create(OnSubscribe)} to create an Observable, instead of this constructor, + * Note: Use {@link #unsafeCreate(OnSubscribe)} to create an Observable, instead of this constructor, * unless you specifically have a need for inheritance. * * @param f @@ -62,10 +62,71 @@ protected Observable(OnSubscribe f) { } /** - * This method requires advanced knowledge about building operators and data sources; please consider - * other standard methods first; - * Returns an Observable that will execute the specified function when a {@link Subscriber} subscribes to - * it. + * Constructs an Observable in an unsafe manner, that is, unsubscription and backpressure handling + * is the responsibility of the OnSubscribe implementation. + * @param the value type emitted + * @param f the callback to execute for each individual Subscriber that subscribes to the + * returned Observable + * @return the new Observable instance + * @deprecated 1.2.7 - inherently unsafe, use the other create() methods for basic cases or + * see {@link #unsafeCreate(OnSubscribe)} for advanced cases (such as custom operators) + * @see #create(SyncOnSubscribe) + * @see #create(AsyncOnSubscribe) + * @see #create(Action1, rx.Emitter.BackpressureMode) + */ + @Deprecated + public static Observable create(OnSubscribe f) { + return new Observable(RxJavaHooks.onCreate(f)); + } + + /** + * Provides an API (via a cold Observable) that bridges the reactive world with the callback-style, + * generally non-backpressured world. + *

+ * Example: + *


+     * Observable.<Event>create(emitter -> {
+     *     Callback listener = new Callback() {
+     *         @Override
+     *         public void onEvent(Event e) {
+     *             emitter.onNext(e);
+     *             if (e.isLast()) {
+     *                 emitter.onCompleted();
+     *             }
+     *         }
+     *
+     *         @Override
+     *         public void onFailure(Exception e) {
+     *             emitter.onError(e);
+     *         }
+     *     };
+     *
+     *     AutoCloseable c = api.someMethod(listener);
+     *
+     *     emitter.setCancellation(c::close);
+     *
+     * }, BackpressureMode.BUFFER);
+     * 
+ *

+ * You should call the Emitter's onNext, onError and onCompleted methods in a serialized fashion. The + * rest of its methods are thread-safe. + *

History: 1.2.7 - experimental + * @param the element type + * @param emitter the emitter that is called when a Subscriber subscribes to the returned {@code Observable} + * @param backpressure the backpressure mode to apply if the downstream Subscriber doesn't request (fast) enough + * @return the new Observable instance + * @see Emitter + * @see Emitter.BackpressureMode + * @see rx.functions.Cancellable + * @since 1.3 + */ + public static Observable create(Action1> emitter, Emitter.BackpressureMode backpressure) { + return unsafeCreate(new OnSubscribeCreate(emitter, backpressure)); + } + + /** + * Returns an Observable that executes the given OnSubscribe action for each individual Subscriber + * that subscribes; unsubscription and backpressure must be implemented manually. *

* *

@@ -84,9 +145,9 @@ protected Observable(OnSubscribe f) { * document the fact that the consumer of the returned {@code Observable} has to apply one of * the {@code onBackpressureXXX} operators. *

Scheduler:
- *
{@code create} does not operate by default on a particular {@link Scheduler}.
+ *
{@code unsafeCreate} does not operate by default on a particular {@link Scheduler}.
* - * + *

History: 1.2.7 - experimental * @param * the type of the items that this Observable emits * @param f @@ -95,8 +156,9 @@ protected Observable(OnSubscribe f) { * @return an Observable that, when a {@link Subscriber} subscribes to it, will execute the specified * function * @see ReactiveX operators documentation: Create + * @since 1.3 */ - public static Observable create(OnSubscribe f) { + public static Observable unsafeCreate(OnSubscribe f) { return new Observable(RxJavaHooks.onCreate(f)); } @@ -111,7 +173,7 @@ public static Observable create(OnSubscribe f) { * {@link Observable#create(AsyncOnSubscribe) asynchronous overload}. * *

- * + * *

* See Rx Design Guidelines (PDF) for detailed * information. @@ -140,7 +202,7 @@ public static Observable create(OnSubscribe f) { * @since 1.2 */ public static Observable create(SyncOnSubscribe syncOnSubscribe) { - return create((OnSubscribe)syncOnSubscribe); + return unsafeCreate(syncOnSubscribe); } /** @@ -153,7 +215,7 @@ public static Observable create(SyncOnSubscribe syncOnSubscribe) * with the {@link Observable#create(SyncOnSubscribe) synchronous overload}. * *

- * + * *

* See Rx Design Guidelines (PDF) for detailed * information. @@ -179,11 +241,11 @@ public static Observable create(SyncOnSubscribe syncOnSubscribe) * @see AsyncOnSubscribe#createStateless(Action2) * @see AsyncOnSubscribe#createStateless(Action2, Action0) * @see ReactiveX operators documentation: Create - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 - beta */ - @Experimental + @Beta public static Observable create(AsyncOnSubscribe asyncOnSubscribe) { - return create((OnSubscribe)asyncOnSubscribe); + return unsafeCreate(asyncOnSubscribe); } /** @@ -234,7 +296,7 @@ public interface Operator extends Func1, Subscriber< * @see RxJava wiki: Implementing Your Own Operators */ public final Observable lift(final Operator operator) { - return create(new OnSubscribeLift(onSubscribe, operator)); + return unsafeCreate(new OnSubscribeLift(onSubscribe, operator)); } /** @@ -286,8 +348,8 @@ public interface Transformer extends Func1, Observable> { * @param the resulting object type * @param converter the function that receives the current Observable instance and returns a value * @return the value returned by the function + * @since 1.3 */ - @Experimental public final R to(Func1, R> converter) { return converter.call(this); } @@ -323,7 +385,7 @@ public Single toSingle() { * {@code ignoreAllElements()}) and calls onCompleted when this source observable calls * onCompleted. Error terminal events are propagated. *

- * *

@@ -338,10 +400,8 @@ public Single toSingle() { * calls onCompleted * @see ReactiveX documentation: * Completable - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number) + * @since 1.3 */ - @Beta public Completable toCompletable() { return Completable.fromObservable(this); } @@ -373,7 +433,7 @@ public Completable toCompletable() { * @see ReactiveX operators documentation: Amb */ public static Observable amb(Iterable> sources) { - return create(OnSubscribeAmb.amb(sources)); + return unsafeCreate(OnSubscribeAmb.amb(sources)); } /** @@ -399,7 +459,7 @@ public static Observable amb(Iterable> * @see ReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2) { - return create(OnSubscribeAmb.amb(o1, o2)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2)); } /** @@ -427,7 +487,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3) { - return create(OnSubscribeAmb.amb(o1, o2, o3)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3)); } /** @@ -457,7 +517,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4)); } /** @@ -489,7 +549,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5)); } /** @@ -523,7 +583,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6)); } /** @@ -559,7 +619,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7)); } /** @@ -597,7 +657,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8)); } /** @@ -637,7 +697,7 @@ public static Observable amb(Observable o1, ObservableReactiveX operators documentation: Amb */ public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Observable o9) { - return create(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8, o9)); + return unsafeCreate(OnSubscribeAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8, o9)); } /** @@ -1020,7 +1080,7 @@ public static Observable combineLates * @see ReactiveX operators documentation: CombineLatest */ public static Observable combineLatest(List> sources, FuncN combineFunction) { - return create(new OnSubscribeCombineLatest(sources, combineFunction)); + return unsafeCreate(new OnSubscribeCombineLatest(sources, combineFunction)); } /** @@ -1049,7 +1109,7 @@ public static Observable combineLatest(ListReactiveX operators documentation: CombineLatest */ public static Observable combineLatest(Iterable> sources, FuncN combineFunction) { - return create(new OnSubscribeCombineLatest(sources, combineFunction)); + return unsafeCreate(new OnSubscribeCombineLatest(sources, combineFunction)); } /** @@ -1080,7 +1140,7 @@ public static Observable combineLatest(IterableReactiveX operators documentation: CombineLatest */ public static Observable combineLatestDelayError(Iterable> sources, FuncN combineFunction) { - return create(new OnSubscribeCombineLatest(null, sources, combineFunction, RxRingBuffer.SIZE, true)); + return unsafeCreate(new OnSubscribeCombineLatest(null, sources, combineFunction, RxRingBuffer.SIZE, true)); } /** @@ -1430,10 +1490,9 @@ public static Observable concat(Observable t1, Observable the common element base type * @param sources the Observable sequence of Observables * @return the new Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ @SuppressWarnings({ "rawtypes", "unchecked" }) - @Beta public static Observable concatDelayError(Observable> sources) { return sources.concatMapDelayError((Func1)UtilityFunctions.identity()); } @@ -1455,9 +1514,8 @@ public static Observable concatDelayError(Observable the common element base type * @param sources the Iterable sequence of Observables * @return the new Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Iterable> sources) { return concatDelayError(from(sources)); } @@ -1482,9 +1540,8 @@ public static Observable concatDelayError(Iterable Observable concatDelayError(Observable t1, Observable t2) { return concatDelayError(just(t1, t2)); } @@ -1511,9 +1568,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t3 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2,Observable t3 ) { return concatDelayError(just(t1, t2, t3)); } @@ -1542,9 +1598,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t4 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4) { return concatDelayError(just(t1, t2, t3, t4)); } @@ -1575,9 +1630,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t5 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5) { return concatDelayError(just(t1, t2, t3, t4, t5)); } @@ -1610,9 +1664,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t6 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6) { return concatDelayError(just(t1, t2, t3, t4, t5, t6)); } @@ -1647,9 +1700,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t7 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7) { return concatDelayError(just(t1, t2, t3, t4, t5, t6, t7)); } @@ -1686,9 +1738,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t8 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8) { return concatDelayError(just(t1, t2, t3, t4, t5, t6, t7, t8)); } @@ -1727,9 +1778,8 @@ public static Observable concatDelayError(Observable t1, Obs * @param t9 * an Observable to be concatenated * @return an Observable with the concatenating behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable concatDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8, Observable t9) { return concatDelayError(just(t1, t2, t3, t4, t5, t6, t7, t8, t9)); } @@ -1762,7 +1812,7 @@ public static Observable concatDelayError(Observable t1, Obs * @see ReactiveX operators documentation: Defer */ public static Observable defer(Func0> observableFactory) { - return create(new OnSubscribeDefer(observableFactory)); + return unsafeCreate(new OnSubscribeDefer(observableFactory)); } /** @@ -1808,7 +1858,7 @@ public static Observable empty() { * @see ReactiveX operators documentation: Throw */ public static Observable error(Throwable exception) { - return create(new OnSubscribeThrow(exception)); + return unsafeCreate(new OnSubscribeThrow(exception)); } /** @@ -1838,7 +1888,7 @@ public static Observable error(Throwable exception) { */ @SuppressWarnings("cast") public static Observable from(Future future) { - return (Observable)create(OnSubscribeToObservableFuture.toObservableFuture(future)); + return (Observable)unsafeCreate(OnSubscribeToObservableFuture.toObservableFuture(future)); } /** @@ -1872,7 +1922,7 @@ public static Observable from(Future future) { */ @SuppressWarnings("cast") public static Observable from(Future future, long timeout, TimeUnit unit) { - return (Observable)create(OnSubscribeToObservableFuture.toObservableFuture(future, timeout, unit)); + return (Observable)unsafeCreate(OnSubscribeToObservableFuture.toObservableFuture(future, timeout, unit)); } /** @@ -1904,7 +1954,7 @@ public static Observable from(Future future, long timeout, T public static Observable from(Future future, Scheduler scheduler) { // TODO in a future revision the Scheduler will become important because we'll start polling instead of blocking on the Future @SuppressWarnings("cast") - Observable o = (Observable)create(OnSubscribeToObservableFuture.toObservableFuture(future)); + Observable o = (Observable)unsafeCreate(OnSubscribeToObservableFuture.toObservableFuture(future)); return o.subscribeOn(scheduler); } @@ -1929,7 +1979,7 @@ public static Observable from(Future future, Scheduler sched * @see ReactiveX operators documentation: From */ public static Observable from(Iterable iterable) { - return create(new OnSubscribeFromIterable(iterable)); + return unsafeCreate(new OnSubscribeFromIterable(iterable)); } /** @@ -1959,53 +2009,7 @@ public static Observable from(T[] array) { if (n == 1) { return just(array[0]); } - return create(new OnSubscribeFromArray(array)); - } - - /** - * Provides an API (via a cold Observable) that bridges the reactive world with the callback-style, - * generally non-backpressured world. - *

- * Example: - *


-     * Observable.<Event>fromEmitter(emitter -> {
-     *     Callback listener = new Callback() {
-     *         @Override
-     *         public void onEvent(Event e) {
-     *             emitter.onNext(e);
-     *             if (e.isLast()) {
-     *                 emitter.onCompleted();
-     *             }
-     *         }
-     *
-     *         @Override
-     *         public void onFailure(Exception e) {
-     *             emitter.onError(e);
-     *         }
-     *     };
-     *
-     *     AutoCloseable c = api.someMethod(listener);
-     *
-     *     emitter.setCancellation(c::close);
-     *
-     * }, BackpressureMode.BUFFER);
-     * 
- *

- * You should call the Emitter's onNext, onError and onCompleted methods in a serialized fashion. The - * rest of its methods are thread-safe. - * - * @param the element type - * @param emitter the emitter that is called when a Subscriber subscribes to the returned {@code Observable} - * @param backpressure the backpressure mode to apply if the downstream Subscriber doesn't request (fast) enough - * @return the new Observable instance - * @see Emitter - * @see Emitter.BackpressureMode - * @see rx.functions.Cancellable - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) - */ - @Experimental - public static Observable fromEmitter(Action1> emitter, Emitter.BackpressureMode backpressure) { - return create(new OnSubscribeFromEmitter(emitter, backpressure)); + return unsafeCreate(new OnSubscribeFromArray(array)); } /** @@ -2033,7 +2037,7 @@ public static Observable fromEmitter(Action1> emitter, Emitter * @since 1.2 */ public static Observable fromCallable(Callable func) { - return create(new OnSubscribeFromCallable(func)); + return unsafeCreate(new OnSubscribeFromCallable(func)); } /** @@ -2140,7 +2144,7 @@ public static Observable interval(long initialDelay, long period, TimeUnit * @since 1.0.12 */ public static Observable interval(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { - return create(new OnSubscribeTimerPeriodically(initialDelay, period, unit, scheduler)); + return unsafeCreate(new OnSubscribeTimerPeriodically(initialDelay, period, unit, scheduler)); } /** @@ -3002,9 +3006,8 @@ public static Observable mergeDelayError(ObservableReactiveX operators documentation: Merge - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable mergeDelayError(Observable> source, int maxConcurrent) { return source.lift(OperatorMerge.instance(true, maxConcurrent)); } @@ -3473,7 +3476,7 @@ public static Observable range(int start, int count) { if (count == 1) { return Observable.just(start); } - return Observable.create(new OnSubscribeRange(start, start + (count - 1))); + return Observable.unsafeCreate(new OnSubscribeRange(start, start + (count - 1))); } /** @@ -3618,10 +3621,8 @@ public static Observable switchOnNext(ObservableReactiveX operators documentation: Switch - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable switchOnNextDelayError(Observable> sequenceOfSequences) { return sequenceOfSequences.lift(OperatorSwitch.instance(true)); } @@ -3733,7 +3734,7 @@ public static Observable timer(long delay, TimeUnit unit) { * @see ReactiveX operators documentation: Timer */ public static Observable timer(long delay, TimeUnit unit, Scheduler scheduler) { - return create(new OnSubscribeTimerOnce(delay, unit, scheduler)); + return unsafeCreate(new OnSubscribeTimerOnce(delay, unit, scheduler)); } /** @@ -3795,15 +3796,13 @@ public static Observable using( * a terminal event ({@code onComplete} or {@code onError}). * @return the Observable whose lifetime controls the lifetime of the dependent resource object * @see ReactiveX operators documentation: Using - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Observable using( final Func0 resourceFactory, final Func1> observableFactory, final Action1 disposeAction, boolean disposeEagerly) { - return create(new OnSubscribeUsing(resourceFactory, observableFactory, disposeAction, disposeEagerly)); + return unsafeCreate(new OnSubscribeUsing(resourceFactory, observableFactory, disposeAction, disposeEagerly)); } /** @@ -3900,8 +3899,8 @@ public static Observable zip(Iterable> ws, FuncN< * an item that will be emitted by the resulting Observable * @return an Observable that emits the zipped results * @see ReactiveX operators documentation: Zip + * @since 1.3 */ - @Experimental public static Observable zip(Observable[] ws, FuncN zipFunction) { return Observable.just(ws).lift(new OperatorZip(zipFunction)); } @@ -5070,7 +5069,7 @@ public final Observable collect(Func0 stateFactory, final Action2(this, stateFactory, collector)); + return unsafeCreate(new OnSubscribeCollect(this, stateFactory, collector)); } /** @@ -5103,7 +5102,7 @@ public final Observable concatMap(Func1 scalar = (ScalarSynchronousObservable) this; return scalar.scalarFlatMap(func); } - return create(new OnSubscribeConcatMap(this, func, 2, OnSubscribeConcatMap.IMMEDIATE)); + return unsafeCreate(new OnSubscribeConcatMap(this, func, 2, OnSubscribeConcatMap.IMMEDIATE)); } /** @@ -5126,15 +5125,14 @@ public final Observable concatMap(Func1 the result value type * @param func the function that maps the items of this Observable into the inner Observables. * @return the new Observable instance with the concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable concatMapDelayError(Func1> func) { if (this instanceof ScalarSynchronousObservable) { ScalarSynchronousObservable scalar = (ScalarSynchronousObservable) this; return scalar.scalarFlatMap(func); } - return create(new OnSubscribeConcatMap(this, func, 2, OnSubscribeConcatMap.END)); + return unsafeCreate(new OnSubscribeConcatMap(this, func, 2, OnSubscribeConcatMap.END)); } /** @@ -5390,6 +5388,8 @@ public final Observable defaultIfEmpty(final T defaultValue) { * Returns an Observable that emits the items emitted by the source Observable or the items of an alternate * Observable if the source Observable is empty. *

+ *

+ * *

*
Backpressure:
*
If the source {@code Observable} is empty, the alternate {@code Observable} is expected to honor backpressure. @@ -5413,7 +5413,7 @@ public final Observable switchIfEmpty(Observable alternate) { if (alternate == null) { throw new NullPointerException("alternate is null"); } - return lift(new OperatorSwitchIfEmpty(alternate)); + return unsafeCreate(new OnSubscribeSwitchIfEmpty(this, alternate)); } /** @@ -5578,7 +5578,7 @@ public final Observable delaySubscription(long delay, TimeUnit unit) { * @see ReactiveX operators documentation: Delay */ public final Observable delaySubscription(long delay, TimeUnit unit, Scheduler scheduler) { - return create(new OnSubscribeDelaySubscription(this, delay, unit, scheduler)); + return unsafeCreate(new OnSubscribeDelaySubscription(this, delay, unit, scheduler)); } /** @@ -5604,7 +5604,7 @@ public final Observable delaySubscription(long delay, TimeUnit unit, Schedule * @see ReactiveX operators documentation: Delay */ public final Observable delaySubscription(Func0> subscriptionDelay) { - return create(new OnSubscribeDelaySubscriptionWithSelector(this, subscriptionDelay)); + return unsafeCreate(new OnSubscribeDelaySubscriptionWithSelector(this, subscriptionDelay)); } /** @@ -5624,14 +5624,13 @@ public final Observable delaySubscription(Func0> * to this Observable. * @return an Observable that delays the subscription to this Observable * until the other Observable emits an element or completes normally. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable delaySubscription(Observable other) { if (other == null) { throw new NullPointerException(); } - return create(new OnSubscribeDelaySubscriptionOther(this, other)); + return unsafeCreate(new OnSubscribeDelaySubscriptionOther(this, other)); } /** @@ -5768,10 +5767,8 @@ public final Observable distinctUntilChanged(Func1ReactiveX operators documentation: Distinct - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number) + * @since 1.3 */ - @Beta public final Observable distinctUntilChanged(Func2 comparator) { return lift(new OperatorDistinctUntilChanged(comparator)); } @@ -5798,7 +5795,7 @@ public final Observable doOnCompleted(final Action0 onCompleted) { Action1 onError = Actions.empty(); Observer observer = new ActionObserver(onNext, onError, onCompleted); - return create(new OnSubscribeDoOnEach(this, observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -5820,7 +5817,7 @@ public final Observable doOnCompleted(final Action0 onCompleted) { */ public final Observable doOnEach(final Action1> onNotification) { Observer observer = new ActionNotificationObserver(onNotification); - return create(new OnSubscribeDoOnEach(this, observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -5847,7 +5844,7 @@ public final Observable doOnEach(final Action1> onNot * @see ReactiveX operators documentation: Do */ public final Observable doOnEach(Observer observer) { - return create(new OnSubscribeDoOnEach(this, observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -5875,7 +5872,7 @@ public final Observable doOnError(final Action1 onError) { Action0 onCompleted = Actions.empty(); Observer observer = new ActionObserver(onNext, onError, onCompleted); - return create(new OnSubscribeDoOnEach(this, observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -5900,7 +5897,7 @@ public final Observable doOnNext(final Action1 onNext) { Action0 onCompleted = Actions.empty(); Observer observer = new ActionObserver(onNext, onError, onCompleted); - return create(new OnSubscribeDoOnEach(this, observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -5981,7 +5978,7 @@ public final Observable doOnTerminate(final Action0 onTerminate) { Observer observer = new ActionObserver(onNext, onError, onTerminate); - return create(new OnSubscribeDoOnEach(this, observer)); + return unsafeCreate(new OnSubscribeDoOnEach(this, observer)); } /** @@ -6030,9 +6027,8 @@ public final Observable doOnUnsubscribe(final Action0 unsubscribe) { * @param o1 the first source * @param o2 the second source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager(Observable o1, Observable o2) { return concatEager(Arrays.asList(o1, o2)); @@ -6056,9 +6052,8 @@ public static Observable concatEager(Observable o1, Observab * @param o2 the second source * @param o3 the third source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6086,9 +6081,8 @@ public static Observable concatEager( * @param o3 the third source * @param o4 the fourth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6117,9 +6111,8 @@ public static Observable concatEager( * @param o4 the fourth source * @param o5 the fifth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6150,9 +6143,8 @@ public static Observable concatEager( * @param o5 the fifth source * @param o6 the sixth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6184,9 +6176,8 @@ public static Observable concatEager( * @param o6 the sixth source * @param o7 the seventh source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6220,9 +6211,8 @@ public static Observable concatEager( * @param o7 the seventh source * @param o8 the eighth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6257,9 +6247,8 @@ public static Observable concatEager( * @param o8 the eighth source * @param o9 the ninth source * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings("unchecked") public static Observable concatEager( Observable o1, Observable o2, @@ -6287,9 +6276,8 @@ public static Observable concatEager( * @param the value type * @param sources a sequence of Observables that need to be eagerly concatenated * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings({ "unchecked", "rawtypes" }) public static Observable concatEager(Iterable> sources) { return Observable.from(sources).concatMapEager((Func1)UtilityFunctions.identity()); @@ -6312,9 +6300,8 @@ public static Observable concatEager(Iterable Observable concatEager(Iterable> sources, int capacityHint) { return Observable.from(sources).concatMapEager((Func1)UtilityFunctions.identity(), capacityHint); @@ -6336,9 +6323,8 @@ public static Observable concatEager(Iterable the value type * @param sources a sequence of Observables that need to be eagerly concatenated * @return the new Observable instance with the specified concatenation behavior - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta @SuppressWarnings({ "unchecked", "rawtypes" }) public static Observable concatEager(Observable> sources) { return sources.concatMapEager((Func1)UtilityFunctions.identity()); @@ -6361,9 +6347,8 @@ public static Observable concatEager(Observable Observable concatEager(Observable> sources, int capacityHint) { return sources.concatMapEager((Func1)UtilityFunctions.identity(), capacityHint); @@ -6387,9 +6372,8 @@ public static Observable concatEager(Observable Observable concatMapEager(Func1> mapper) { return concatMapEager(mapper, RxRingBuffer.SIZE); } @@ -6413,9 +6397,8 @@ public final Observable concatMapEager(Func1 Observable concatMapEager(Func1> mapper, int capacityHint) { if (capacityHint < 1) { throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint); @@ -6443,9 +6426,8 @@ public final Observable concatMapEager(Func1 Observable concatMapEager(Func1> mapper, int capacityHint, int maxConcurrent) { if (capacityHint < 1) { throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint); @@ -6557,7 +6539,7 @@ public final Observable exists(Func1 predicate) { * @see ReactiveX operators documentation: Filter */ public final Observable filter(Func1 predicate) { - return create(new OnSubscribeFilter(this, predicate)); + return unsafeCreate(new OnSubscribeFilter(this, predicate)); } /** @@ -6905,6 +6887,74 @@ public final Observable flatMap(final Func1(collectionSelector, resultSelector)), maxConcurrent); } + /** + * Maps all upstream values to Completables and runs them together until the upstream + * and all inner Completables complete normally. + *
+ *
Backpressure:
+ *
The operator consumes items from upstream in an unbounded manner and ignores downstream backpressure + * as it doesn't emit items but only terminal event.
+ *
Scheduler:
+ *
{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param mapper the function that receives an upstream value and turns it into a Completable + * to be merged. + * @return the new Observable instance + * @see #flatMapCompletable(Func1, boolean, int) + * @since 1.3 + */ + public final Observable flatMapCompletable(Func1 mapper) { + return flatMapCompletable(mapper, false, Integer.MAX_VALUE); + } + + /** + * Maps all upstream values to Completables and runs them together, optionally delaying any errors, until the upstream + * and all inner Completables terminate. + *

+ *
Backpressure:
+ *
The operator consumes items from upstream in an unbounded manner and ignores downstream backpressure + * as it doesn't emit items but only terminal event.
+ *
Scheduler:
+ *
{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param mapper the function that receives an upstream value and turns it into a Completable + * to be merged. + * @param delayErrors if true, errors from the upstream and from the inner Completables get delayed till + * the all of them terminate. + * @return the new Observable instance + * @since 1.3 + * @see #flatMapCompletable(Func1, boolean, int) + */ + public final Observable flatMapCompletable(Func1 mapper, boolean delayErrors) { + return flatMapCompletable(mapper, delayErrors, Integer.MAX_VALUE); + } + + /** + * Maps upstream values to Completables and runs up to the given number of them together at a time, + * optionally delaying any errors, until the upstream and all inner Completables terminate. + *

+ *
Backpressure:
+ *
The operator consumes at most maxConcurrent items from upstream and one-by-one after as the inner + * Completables terminate. The operator ignores downstream backpressure as it doesn't emit items but + * only the terminal event.
+ *
Scheduler:
+ *
{@code flatMapCompletable} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param mapper the function that receives an upstream value and turns it into a Completable + * to be merged. + * @param delayErrors if true, errors from the upstream and from the inner Completables get delayed till + * the all of them terminate. + * @param maxConcurrency the maximum number of inner Completables to run at a time + * @return the new Observable instance + * @since 1.3 + */ + public final Observable flatMapCompletable(Func1 mapper, boolean delayErrors, int maxConcurrency) { + return unsafeCreate(new OnSubscribeFlatMapCompletable(this, mapper, delayErrors, maxConcurrency)); + } + /** * Returns an Observable that merges each item emitted by the source Observable with the values in an * Iterable corresponding to that item that is generated by a selector. @@ -7038,6 +7088,74 @@ public final Observable flatMapIterable(Func1)flatMap(OperatorMapPair.convertSelector(collectionSelector), resultSelector, maxConcurrent); } + /** + * Maps all upstream values to Singles and runs them together until the upstream + * and all inner Singles complete normally. + *

+ *
Backpressure:
+ *
The operator consumes items from upstream in an unbounded manner and honors downstream backpressure.
+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param mapper the function that receives an upstream value and turns it into a Single + * to be merged. + * @return the new Observable instance + * @see #flatMapSingle(Func1, boolean, int) + * @since 1.3 + */ + public final Observable flatMapSingle(Func1> mapper) { + return flatMapSingle(mapper, false, Integer.MAX_VALUE); + } + + /** + * Maps all upstream values to Singles and runs them together, optionally delaying any errors, until the upstream + * and all inner Singles terminate. + *

+ *
Backpressure:
+ *
The operator consumes items from upstream in an unbounded manner and honors downstream backpressure.
+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param mapper the function that receives an upstream value and turns it into a Single + * to be merged. + * @param delayErrors if true, errors from the upstream and from the inner Singles get delayed till + * the all of them terminate. + * @return the new Observable instance + * @since 1.3 + * @see #flatMapSingle(Func1, boolean, int) + */ + public final Observable flatMapSingle(Func1> mapper, boolean delayErrors) { + return flatMapSingle(mapper, delayErrors, Integer.MAX_VALUE); + } + + /** + * Maps upstream values to Singles and runs up to the given number of them together at a time, + * optionally delaying any errors, until the upstream and all inner Singles terminate. + *

+ *
Backpressure:
+ *
The operator consumes at most maxConcurrent items from upstream and one-by-one after as the inner + * Singles terminate. The operator honors downstream backpressure.
+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param mapper the function that receives an upstream value and turns it into a Single + * to be merged. + * @param delayErrors if true, errors from the upstream and from the inner Singles get delayed till + * the all of them terminate. + * @param maxConcurrency the maximum number of inner Singles to run at a time + * @return the new Observable instance + * @since 1.3 + */ + public final Observable flatMapSingle(Func1> mapper, boolean delayErrors, int maxConcurrency) { + return unsafeCreate(new OnSubscribeFlatMapSingle(this, mapper, delayErrors, maxConcurrency)); + } + /** * Subscribes to the {@link Observable} and receives notifications for each element. *

@@ -7157,7 +7275,7 @@ public final void forEach(final Action1 onNext, final Action1ReactiveX operators documentation: GroupBy */ public final Observable> groupBy(final Func1 keySelector, final Func1 elementSelector) { - return lift(new OperatorGroupBy(keySelector, elementSelector)); + return lift(new OperatorGroupByEvicting(keySelector, elementSelector)); } /** @@ -7215,8 +7333,13 @@ public final Observable> groupBy(final Func1ReactiveX operators documentation: GroupBy + * @since 1.3 + * @deprecated since 1.3.7, use {@link #groupBy(Func1, Func1, int, boolean, Func1)} + * instead which uses much less memory. Please take note of the + * usage difference involving the evicting action which now expects + * the value from the map instead of the key. */ - @Experimental + @Deprecated public final Observable> groupBy(final Func1 keySelector, final Func1 elementSelector, final Func1, Map> evictingMapFactory) { if (evictingMapFactory == null) { @@ -7251,6 +7374,72 @@ public final Observable> groupBy(final Func1 + * {@code + * Func1, Map> mapFactory + * = action -> CacheBuilder.newBuilder() + * .maximumSize(1000) + * .expireAfterAccess(12, TimeUnit.HOURS) + * .removalListener(entry -> action.call(entry.getValue())) + * . build().asMap(); + * } + * + * + * @param + * the key type + * @param + * the element type + * @return an {@code Observable} that emits {@link GroupedObservable}s, each of which corresponds to a + * unique key value and each of which emits those items from the source Observable that share that + * key value + * @throws NullPointerException + * if {@code evictingMapFactory} is null + * @see ReactiveX operators documentation: GroupBy + * @since 1.3.7 + */ + @Experimental + public final Observable> groupBy(final Func1 keySelector, + final Func1 elementSelector, int bufferSize, boolean delayError, + final Func1, Map> evictingMapFactory) { + if (evictingMapFactory == null) { + throw new NullPointerException("evictingMapFactory cannot be null"); + } + return lift(new OperatorGroupByEvicting( + keySelector, elementSelector, bufferSize, delayError, evictingMapFactory)); + } + + /** + * Groups the items emitted by an {@code Observable} according to a specified criterion, and emits these + * grouped items as {@link GroupedObservable}s. The emitted {@code GroupedObservable} allows only a single + * {@link Subscriber} during its lifetime and if this {@code Subscriber} unsubscribes before the + * source terminates, the next emission by the source having the same key will trigger a new + * {@code GroupedObservable} emission. + *

+ * + *

+ * Note: A {@link GroupedObservable} will cache the items it is to emit until such time as it + * is subscribed to. For this reason, in order to avoid memory leaks, you should not simply ignore those + * {@code GroupedObservable}s that do not concern you. Instead, you can signal to them that they may + * discard their buffers by applying an operator like {@link #ignoreElements} to them. + *

+ *
Scheduler:
+ *
{@code groupBy} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param keySelector + * a function that extracts the key for each item * @param * the key type * @return an {@code Observable} that emits {@link GroupedObservable}s, each of which corresponds to a @@ -7259,7 +7448,7 @@ public final Observable> groupBy(final Func1ReactiveX operators documentation: GroupBy */ public final Observable> groupBy(final Func1 keySelector) { - return lift(new OperatorGroupBy(keySelector)); + return lift(new OperatorGroupByEvicting(keySelector)); } /** @@ -7299,7 +7488,7 @@ public final Observable> groupBy(final Func1 Observable groupJoin(Observable right, Func1> leftDuration, Func1> rightDuration, Func2, ? extends R> resultSelector) { - return create(new OnSubscribeGroupJoin(this, right, leftDuration, rightDuration, resultSelector)); + return unsafeCreate(new OnSubscribeGroupJoin(this, right, leftDuration, rightDuration, resultSelector)); } /** @@ -7381,7 +7570,7 @@ public final Observable isEmpty() { public final Observable join(Observable right, Func1> leftDurationSelector, Func1> rightDurationSelector, Func2 resultSelector) { - return create(new OnSubscribeJoin(this, right, leftDurationSelector, rightDurationSelector, resultSelector)); + return unsafeCreate(new OnSubscribeJoin(this, right, leftDurationSelector, rightDurationSelector, resultSelector)); } /** @@ -7529,7 +7718,7 @@ public final Observable limit(int count) { * @see ReactiveX operators documentation: Map */ public final Observable map(Func1 func) { - return create(new OnSubscribeMap(this, func)); + return unsafeCreate(new OnSubscribeMap(this, func)); } private Observable mapNotification(Func1 onNext, Func1 onError, Func0 onCompleted) { @@ -7845,9 +8034,8 @@ public final Observable onBackpressureBuffer(long capacity, Action0 onOverflo * @param overflowStrategy how should the {@code Observable} react to buffer overflows. Null is not allowed. * @return the source {@code Observable} modified to buffer items up to the given capacity * @see ReactiveX operators documentation: backpressure operators - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable onBackpressureBuffer(long capacity, Action0 onOverflow, BackpressureOverflow.Strategy overflowStrategy) { return lift(new OperatorOnBackpressureBuffer(capacity, onOverflow, overflowStrategy)); } @@ -7871,7 +8059,6 @@ public final Observable onBackpressureBuffer(long capacity, Action0 onOverflo * @param onDrop the action to invoke for each item dropped. onDrop action should be fast and should never block. * @return the source Observable modified to drop {@code onNext} notifications on overflow * @see ReactiveX operators documentation: backpressure operators - * @Experimental The behavior of this can change at any time. * @since 1.1.0 */ public final Observable onBackpressureDrop(Action1 onDrop) { @@ -8102,11 +8289,10 @@ public final Observable onExceptionResumeNext(final Observable r *
* @return an Observable which out references to the upstream producer and downstream Subscriber if * the sequence is terminated or downstream unsubscribes - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable onTerminateDetach() { - return create(new OnSubscribeDetach(this)); + return unsafeCreate(new OnSubscribeDetach(this)); } /** @@ -8177,9 +8363,8 @@ public final Observable publish(Func1, ? extends Ob * * @param n the initial request amount, further request will happen after 75% of this value * @return the Observable that rebatches request amounts from downstream - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable rebatchRequests(int n) { if (n <= 0) { throw new IllegalArgumentException("n > 0 required but it was " + n); @@ -8223,7 +8408,7 @@ public final Observable reduce(Func2 accumulator) { * * It should use last() not takeLast(1) since it needs to emit an error if the sequence is empty. */ - return create(new OnSubscribeReduce(this, accumulator)); + return unsafeCreate(new OnSubscribeReduce(this, accumulator)); } /** @@ -8271,7 +8456,7 @@ public final Observable reduce(Func2 accumulator) { * @see Wikipedia: Fold (higher-order function) */ public final Observable reduce(R initialValue, Func2 accumulator) { - return create(new OnSubscribeReduceSeed(this, initialValue, accumulator)); + return unsafeCreate(new OnSubscribeReduceSeed(this, initialValue, accumulator)); } /** @@ -9098,7 +9283,7 @@ public final Observable retryWhen(final Func1 - * + * *
*
Backpressure:
*
This operator does not support backpressure as it uses time to control data flow.
@@ -9124,7 +9309,7 @@ public final Observable sample(long period, TimeUnit unit) { * Returns an Observable that emits the most recently emitted item (if any) emitted by the source Observable * within periodic time intervals, where the intervals are defined on a particular Scheduler. *

- * + * *

*
Backpressure:
*
This operator does not support backpressure as it uses time to control data flow.
@@ -9484,7 +9669,7 @@ public final Observable skip(long time, TimeUnit unit) { * @see ReactiveX operators documentation: Skip */ public final Observable skip(long time, TimeUnit unit, Scheduler scheduler) { - return create(new OnSubscribeSkipTimed(this, time, unit, scheduler)); + return unsafeCreate(new OnSubscribeSkipTimed(this, time, unit, scheduler)); } /** @@ -10265,11 +10450,15 @@ static Subscription subscribe(Subscriber subscriber, Observable + * If there is a {@link #create(Action1, rx.Emitter.BackpressureMode)} type source up in the + * chain, it is recommended to use {@code subscribeOn(scheduler, false)} instead + * to avoid same-pool deadlock because requests pile up behind a eager/blocking emitter. + *

* *

*
Backpressure:
- *
The operator doesn't interfere with backpressure which is determined by the source {@code Observable}'s backpressure - * behavior.
+ *
The operator doesn't interfere with backpressure amount which is determined by the source {@code Observable}'s backpressure + * behavior. However, the upstream is requested from the given scheduler thread.
*
Scheduler:
*
you specify which {@link Scheduler} this operator will use
*
@@ -10281,12 +10470,47 @@ static Subscription subscribe(Subscriber subscriber, ObservableReactiveX operators documentation: SubscribeOn * @see RxJava Threading Examples * @see #observeOn + * @see #subscribeOn(Scheduler, boolean) */ public final Observable subscribeOn(Scheduler scheduler) { + return subscribeOn(scheduler, !(this.onSubscribe instanceof OnSubscribeCreate)); + } + + /** + * Asynchronously subscribes Observers to this Observable on the specified {@link Scheduler} and + * optionally reroutes requests from other threads to the same {@link Scheduler} thread. + *

+ * If there is a {@link #create(Action1, rx.Emitter.BackpressureMode)} type source up in the + * chain, it is recommended to have {@code requestOn} false to avoid same-pool deadlock + * because requests pile up behind a eager/blocking emitter. + *

+ * + *

+ *
Backpressure:
+ *
The operator doesn't interfere with backpressure amount which is determined by the source {@code Observable}'s backpressure + * behavior. However, the upstream is requested from the given scheduler if requestOn is true.
+ *
Scheduler:
+ *
you specify which {@link Scheduler} this operator will use
+ *
+ *

History: 1.2.7 - experimental + * @param scheduler + * the {@link Scheduler} to perform subscription actions on + * @param requestOn if true, requests are rerouted to the given Scheduler as well (strong pipelining) + * if false, requests coming from any thread are simply forwarded to + * the upstream on the same thread (weak pipelining) + * @return the source Observable modified so that its subscriptions happen on the + * specified {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + * @see RxJava Threading Examples + * @see #observeOn + * @see #subscribeOn(Scheduler) + * @since 1.3 + */ + public final Observable subscribeOn(Scheduler scheduler, boolean requestOn) { if (this instanceof ScalarSynchronousObservable) { return ((ScalarSynchronousObservable)this).scalarScheduleOn(scheduler); } - return create(new OperatorSubscribeOn(this, scheduler)); + return unsafeCreate(new OperatorSubscribeOn(this, scheduler, requestOn)); } /** @@ -10345,10 +10569,8 @@ public final Observable switchMap(Func1ReactiveX operators documentation: FlatMap - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable switchMapDelayError(Func1> func) { return switchOnNextDelayError(map(func)); } @@ -10385,6 +10607,9 @@ public final Observable take(final int count) { * Returns an Observable that emits those items emitted by source Observable before a specified time runs * out. *

+ * If time runs out before the {@code Observable} completes normally, the {@code onComplete} event will be + * signaled on the default {@code computation} {@link Scheduler}. + *

* *

*
Backpressure:
@@ -10409,6 +10634,9 @@ public final Observable take(long time, TimeUnit unit) { * Returns an Observable that emits those items emitted by source Observable before a specified time (on a * specified Scheduler) runs out. *

+ * If time runs out before the {@code Observable} completes normally, the {@code onComplete} event will be + * signaled on the provided {@link Scheduler}. + *

* *

*
Backpressure:
@@ -10481,7 +10709,7 @@ public final Observable takeLast(final int count) { if (count == 0) { return ignoreElements(); } else if (count == 1) { - return create(new OnSubscribeTakeLastOne(this)); + return unsafeCreate(new OnSubscribeTakeLastOne(this)); } else { return lift(new OperatorTakeLast(count)); } @@ -11133,11 +11361,13 @@ public final Observable timeout(Func0> firstTi * if {@code timeoutSelector} is null * @see ReactiveX operators documentation: Timeout */ + @SuppressWarnings("unchecked") public final Observable timeout(Func0> firstTimeoutSelector, Func1> timeoutSelector, Observable other) { if (timeoutSelector == null) { throw new NullPointerException("timeoutSelector is null"); } - return lift(new OperatorTimeoutWithSelector(firstTimeoutSelector, timeoutSelector, other)); + return unsafeCreate(new OnSubscribeTimeoutSelectorWithFallback(this, + firstTimeoutSelector != null ? defer((Func0>)firstTimeoutSelector) : null, timeoutSelector, other)); } /** @@ -11292,7 +11522,7 @@ public final Observable timeout(long timeout, TimeUnit timeUnit, ObservableReactiveX operators documentation: Timeout */ public final Observable timeout(long timeout, TimeUnit timeUnit, Observable other, Scheduler scheduler) { - return lift(new OperatorTimeout(timeout, timeUnit, other, scheduler)); + return unsafeCreate(new OnSubscribeTimeoutTimedWithFallback(this, timeout, timeUnit, scheduler, other)); } /** @@ -11439,7 +11669,7 @@ public final Observable> toList() { * @see ReactiveX operators documentation: To */ public final Observable> toMap(Func1 keySelector) { - return create(new OnSubscribeToMap(this, keySelector, UtilityFunctions.identity())); + return unsafeCreate(new OnSubscribeToMap(this, keySelector, UtilityFunctions.identity())); } /** @@ -11469,7 +11699,7 @@ public final Observable> toMap(Func1 keySe * @see ReactiveX operators documentation: To */ public final Observable> toMap(Func1 keySelector, Func1 valueSelector) { - return create(new OnSubscribeToMap(this, keySelector, valueSelector)); + return unsafeCreate(new OnSubscribeToMap(this, keySelector, valueSelector)); } /** @@ -11498,7 +11728,7 @@ public final Observable> toMap(Func1 ke * @see ReactiveX operators documentation: To */ public final Observable> toMap(Func1 keySelector, Func1 valueSelector, Func0> mapFactory) { - return create(new OnSubscribeToMap(this, keySelector, valueSelector, mapFactory)); + return unsafeCreate(new OnSubscribeToMap(this, keySelector, valueSelector, mapFactory)); } /** @@ -11521,7 +11751,7 @@ public final Observable> toMap(Func1 ke * @see ReactiveX operators documentation: To */ public final Observable>> toMultimap(Func1 keySelector) { - return create(new OnSubscribeToMultimap(this, keySelector, UtilityFunctions.identity())); + return unsafeCreate(new OnSubscribeToMultimap(this, keySelector, UtilityFunctions.identity())); } /** @@ -11549,7 +11779,7 @@ public final Observable>> toMultimap(Func1ReactiveX operators documentation: To */ public final Observable>> toMultimap(Func1 keySelector, Func1 valueSelector) { - return create(new OnSubscribeToMultimap(this, keySelector, valueSelector)); + return unsafeCreate(new OnSubscribeToMultimap(this, keySelector, valueSelector)); } /** @@ -11579,7 +11809,7 @@ public final Observable>> toMultimap(Func1ReactiveX operators documentation: To */ public final Observable>> toMultimap(Func1 keySelector, Func1 valueSelector, Func0>> mapFactory) { - return create(new OnSubscribeToMultimap(this, keySelector, valueSelector, mapFactory)); + return unsafeCreate(new OnSubscribeToMultimap(this, keySelector, valueSelector, mapFactory)); } /** @@ -11611,7 +11841,7 @@ public final Observable>> toMultimap(Func1ReactiveX operators documentation: To */ public final Observable>> toMultimap(Func1 keySelector, Func1 valueSelector, Func0>> mapFactory, Func1> collectionFactory) { - return create(new OnSubscribeToMultimap(this, keySelector, valueSelector, mapFactory, collectionFactory)); + return unsafeCreate(new OnSubscribeToMultimap(this, keySelector, valueSelector, mapFactory, collectionFactory)); } /** @@ -11685,9 +11915,8 @@ public final Observable> toSortedList(Func2ReactiveX operators documentation: To - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable> toSortedList(int initialCapacity) { return lift(new OperatorToObservableSortedList(initialCapacity)); } @@ -11713,9 +11942,8 @@ public final Observable> toSortedList(int initialCapacity) { * @return an Observable that emits a list that contains the items emitted by the source Observable in * sorted order * @see ReactiveX operators documentation: To - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Observable> toSortedList(Func2 sortFunction, int initialCapacity) { return lift(new OperatorToObservableSortedList(sortFunction, initialCapacity)); } @@ -11740,8 +11968,8 @@ public final Observable> toSortedList(Func2 sorted() { return toSortedList().flatMapIterable(UtilityFunctions.>identity()); } @@ -11765,8 +11993,8 @@ public final Observable sorted() { * a function that compares two items emitted by the source Observable and returns an Integer * that indicates their sort order * @return an Observable that emits the items emitted by the source Observable in sorted order + * @since 1.3 */ - @Experimental public final Observable sorted(Func2 sortFunction) { return toSortedList(sortFunction).flatMapIterable(UtilityFunctions.>identity()); } @@ -11817,11 +12045,9 @@ public final Observable unsubscribeOn(Scheduler scheduler) { * @return an Observable that merges the specified Observable into this Observable by using the * {@code resultSelector} function only when the source Observable sequence (this instance) emits an * item - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 * @see ReactiveX operators documentation: CombineLatest */ - @Experimental public final Observable withLatestFrom(Observable other, Func2 resultSelector) { return lift(new OperatorWithLatestFrom(other, resultSelector)); } @@ -11850,12 +12076,10 @@ public final Observable withLatestFrom(Observable other, * @param o2 the second other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom(Observable o1, Observable o2, Func3 combiner) { - return create(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2 }, null, Functions.fromFunc(combiner))); + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2 }, null, Functions.fromFunc(combiner))); } /** @@ -11884,15 +12108,13 @@ public final Observable withLatestFrom(Observable o1, Observa * @param o3 the third other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Func4 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3 }, null, Functions.fromFunc(combiner))); } @@ -11924,15 +12146,13 @@ public final Observable withLatestFrom( * @param o4 the fourth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, Func5 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4 }, null, Functions.fromFunc(combiner))); } /** @@ -11965,16 +12185,14 @@ public final Observable withLatestFrom( * @param o5 the fifth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Func6 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4, o5 }, null, Functions.fromFunc(combiner))); } @@ -12010,16 +12228,14 @@ public final Observable withLatestFrom( * @param o6 the sixth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Func7 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4, o5, o6 }, null, Functions.fromFunc(combiner))); } @@ -12057,17 +12273,15 @@ public final Observable withLatestFrom( * @param o7 the seventh other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Func8 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4, o5, o6, o7 }, null, Functions.fromFunc(combiner))); } @@ -12107,17 +12321,15 @@ public final Observable withLatestFrom( * @param o8 the eighth other Observable * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom( Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Func9 combiner) { - return create(new OperatorWithLatestFromMany(this, + return unsafeCreate(new OperatorWithLatestFromMany(this, new Observable[] { o1, o2, o3, o4, o5, o6, o7, o8 }, null, Functions.fromFunc(combiner))); } @@ -12142,12 +12354,10 @@ public final Observable withLatestFrom( * @param others the array of other sources * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom(Observable[] others, FuncN combiner) { - return create(new OperatorWithLatestFromMany(this, others, null, combiner)); + return unsafeCreate(new OperatorWithLatestFromMany(this, others, null, combiner)); } /** @@ -12171,12 +12381,10 @@ public final Observable withLatestFrom(Observable[] others, FuncN c * @param others the iterable of other sources * @param combiner the function called with an array of values from each participating observable * @return the new Observable instance - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Observable withLatestFrom(Iterable> others, FuncN combiner) { - return create(new OperatorWithLatestFromMany(this, null, others, combiner)); + return unsafeCreate(new OperatorWithLatestFromMany(this, null, others, combiner)); } /** @@ -12655,10 +12863,10 @@ public final Observable zipWith(Observable other, Func2 *
Scheduler:
*
{@code test} does not operate by default on a particular {@link Scheduler}.
*
+ *

History: 1.2.3 - experimental * @return the new AssertableSubscriber instance - * @since 1.2.3 + * @since 1.3 */ - @Experimental public final AssertableSubscriber test() { AssertableSubscriber ts = AssertableSubscriberObservable.create(Long.MAX_VALUE); subscribe(ts); @@ -12674,11 +12882,11 @@ public final AssertableSubscriber test() { *

Scheduler:
*
{@code test} does not operate by default on a particular {@link Scheduler}.
*
+ *

History: 1.2.3 - experimental * @return the new AssertableSubscriber instance * @param initialRequestAmount the amount to request from upstream upfront, non-negative (not verified) - * @since 1.2.3 + * @since 1.3 */ - @Experimental public final AssertableSubscriber test(long initialRequestAmount) { AssertableSubscriber ts = AssertableSubscriberObservable.create(initialRequestAmount); subscribe(ts); diff --git a/src/main/java/rx/Scheduler.java b/src/main/java/rx/Scheduler.java index b98615ff47..89259b178b 100644 --- a/src/main/java/rx/Scheduler.java +++ b/src/main/java/rx/Scheduler.java @@ -17,7 +17,6 @@ import java.util.concurrent.TimeUnit; -import rx.annotations.Experimental; import rx.functions.*; import rx.internal.schedulers.*; import rx.schedulers.Schedulers; @@ -203,9 +202,9 @@ public long now() { * @param combine the function that takes a two-level nested Observable sequence of a Completable and returns * the Completable that will be subscribed to and should trigger the execution of the scheduled Actions. * @return the Scheduler with the customized execution behavior + * @since 1.3 */ @SuppressWarnings("unchecked") - @Experimental public S when(Func1>, Completable> combine) { return (S) new SchedulerWhen(combine, this); } diff --git a/src/main/java/rx/Single.java b/src/main/java/rx/Single.java index 782d5c833f..0cf0d5f06f 100644 --- a/src/main/java/rx/Single.java +++ b/src/main/java/rx/Single.java @@ -145,8 +145,8 @@ public interface OnSubscribe extends Action1> { * the Operator that implements the Single-operating function to be applied to the source Single * @return a Single that is the result of applying the lifted Operator to the source Single * @see RxJava wiki: Implementing Your Own Operators + * @since 1.3 */ - @Beta public final Single lift(final Operator lift) { return create(new SingleLiftObservableOperator(this.onSubscribe, lift)); } @@ -202,7 +202,7 @@ public interface Transformer extends Func1, Single> { */ private static Observable asObservable(Single t) { // is this sufficient, or do I need to keep the outer Single and subscribe to it? - return Observable.create(new SingleToObservable(t.onSubscribe)); + return Observable.unsafeCreate(new SingleToObservable(t.onSubscribe)); } /* ********************************************************************************************************* @@ -210,6 +210,23 @@ private static Observable asObservable(Single t) { * ********************************************************************************************************* */ + /** + * Casts the success value of the current Single into the target type or signals a + * ClassCastException if not compatible. + *

+ *
Scheduler:
+ *
{@code cast} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the target type + * @param klass the type token to use for casting the success result from the current Single + * @return the new Single instance + * @since 1.3.1 - experimental + */ + @Experimental + public final Single cast(final Class klass) { + return map(new SingleOperatorCast(klass)); + } + /** * Returns an Observable that emits the items emitted by two Singles, one after the other. *

@@ -602,12 +619,12 @@ public static Single fromCallable(final Callable func) { * *

All of the SingleEmitter's methods are thread-safe and ensure the * Single's protocol are held. + *

History: 1.2.3 - experimental * @param the success value type * @param producer the callback invoked for each incoming SingleSubscriber * @return the new Single instance - * @since 1.2.3 - experimental (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public static Single fromEmitter(Action1> producer) { if (producer == null) { throw new NullPointerException("producer is null"); } return create(new SingleFromEmitter(producer)); @@ -930,6 +947,97 @@ public static Observable merge(Single t1, Single + *

Backpressure:
+ *
The operator consumes items from the Observable in an unbounded manner and honors downstream backpressure.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param sources the Observable that emits Singles to be merged + * @return the new Observable instance + * @see #merge(Observable, int) + * @see #mergeDelayError(Observable) + * @see #mergeDelayError(Observable, int) + * @since 1.3 + */ + public static Observable merge(Observable> sources) { + return merge(sources, Integer.MAX_VALUE); + } + + /** + * Merges the Singles emitted by the Observable and runs up to the given number of them together at a time, + * until the Observable and all inner Singles terminate. + *

+ *
Backpressure:
+ *
The operator consumes at most maxConcurrent items from the Observable and one-by-one after as the inner + * Singles terminate. The operator ignores downstream backpressure as it doesn't emit items but + * only the terminal event.
+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param sources the Observable that emits Singles to be merged + * @param maxConcurrency the maximum number of inner Singles to run at a time + * @return the new Observable instance + * @since 1.3 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Observable merge(Observable> sources, int maxConcurrency) { + return sources.flatMapSingle((Func1)UtilityFunctions.identity(), false, maxConcurrency); + } + + /** + * Merges all Singles emitted by the Observable and runs them together, + * delaying errors from them and the Observable, until the source + * Observable and all inner Singles complete normally. + *

+ *
Backpressure:
+ *
The operator consumes items from the Observable in an unbounded manner and honors downstream backpressure.
+ *
Scheduler:
+ *
{@code merge} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param sources the Observable that emits Singles to be merged + * @return the new Observable instance + * @see #mergeDelayError(Observable, int) + * @see #merge(Observable) + * @see #merge(Observable, int) + * @since 1.3 + */ + public static Observable mergeDelayError(Observable> sources) { + return merge(sources, Integer.MAX_VALUE); + } + + /** + * Merges the Singles emitted by the Observable and runs up to the given number of them together at a time, + * delaying errors from them and the Observable, until the Observable and all inner Singles terminate. + *

+ *
Backpressure:
+ *
The operator consumes at most maxConcurrent items from the Observable and one-by-one after as the inner + * Singles terminate. The operator ignores downstream backpressure as it doesn't emit items but + * only the terminal event.
+ *
Scheduler:
+ *
{@code flatMapSingle} does not operate by default on a particular {@link Scheduler}.
+ *
+ *

History: 1.2.7 - experimental + * @param the value type of the inner Singles and the resulting Observable + * @param sources the Observable that emits Singles to be merged + * @param maxConcurrency the maximum number of inner Singles to run at a time + * @return the new Observable instance + * @since 1.3 + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Observable mergeDelayError(Observable> sources, int maxConcurrency) { + return sources.flatMapSingle((Func1)UtilityFunctions.identity(), true, maxConcurrency); + } + /** * Returns a Single that emits the results of a specified combiner function applied to two items emitted by * two other Singles. @@ -1361,8 +1469,8 @@ public static Single zip(Iterable> singles, FuncNReactiveX operators documentation: Replay + * @since 1.3 */ - @Experimental public final Single cache() { return toObservable().cacheWithInitialCapacity(1).toSingle(); } @@ -1446,9 +1554,8 @@ public final Observable flatMapObservable(Func1ReactiveX operators documentation: FlatMap - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Completable flatMapCompletable(final Func1 func) { return Completable.create(new CompletableFlatMapSingleToCompletable(this, func)); } @@ -1578,10 +1685,8 @@ public final Single onErrorReturn(Func1 resumeFunctio * @param resumeSingleInCaseOfError a Single that will take control if source Single encounters an error. * @return the original Single, with appropriately modified behavior. * @see ReactiveX operators documentation: Catch - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single onErrorResumeNext(Single resumeSingleInCaseOfError) { return new Single(SingleOperatorOnErrorResumeNext.withOther(this, resumeSingleInCaseOfError)); } @@ -1612,10 +1717,8 @@ public final Single onErrorResumeNext(Single resumeSingleInCaseO * @param resumeFunctionInCaseOfError a function that returns a Single that will take control if source Single encounters an error. * @return the original Single, with appropriately modified behavior. * @see ReactiveX operators documentation: Catch - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single onErrorResumeNext(final Func1> resumeFunctionInCaseOfError) { return new Single(SingleOperatorOnErrorResumeNext.withFunction(this, resumeFunctionInCaseOfError)); } @@ -2026,8 +2129,8 @@ public final Single takeUntil(final Single other) { * @param the resulting object type * @param converter the function that receives the current Single instance and returns a value * @return the value returned by the function + * @since 1.3 */ - @Experimental public final R to(Func1, R> converter) { return converter.call(this); } @@ -2059,10 +2162,8 @@ public final Observable toObservable() { * @return a {@link Completable} that calls {@code onCompleted} on it's subscriber when the source {@link Single} * calls {@code onSuccess}. * @see ReactiveX documentation: Completable - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical - * with the release number). + * @since 1.3 */ - @Beta public final Completable toCompletable() { return Completable.fromSingle(this); } @@ -2163,7 +2264,15 @@ public final Single timeout(long timeout, TimeUnit timeUnit, Single timeout(long timeout, TimeUnit timeUnit, Single other, Scheduler scheduler) { if (other == null) { - other = Single. error(new TimeoutException()); + // Use a defer instead of simply other = Single.error(new TimeoutException()) + // since instantiating an exception will cause the current stack trace to be inspected + // and we only want to incur that overhead when a timeout actually happens. + other = Single.defer(new Func0>() { + @Override + public Single call() { + return Single.error(new TimeoutException()); + } + }); } return create(new SingleTimeout(onSubscribe, timeout, timeUnit, scheduler, other.onSubscribe)); } @@ -2177,8 +2286,8 @@ public final Single timeout(long timeout, TimeUnit timeUnit, SingleReactiveX operators documentation: To + * @since 1.3 */ - @Beta public final BlockingSingle toBlocking() { return BlockingSingle.from(this); } @@ -2227,9 +2336,8 @@ public final Single zipWith(Single other, Func2ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single doOnError(final Action1 onError) { if (onError == null) { throw new IllegalArgumentException("onError is null"); @@ -2256,9 +2364,8 @@ public void call(final Throwable throwable) { * the action to invoke when the source {@link Single} calls {@code onSuccess} or {@code onError}. * @return the source {@link Single} with the side-effecting behavior applied * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Single doOnEach(final Action1> onNotification) { if (onNotification == null) { throw new IllegalArgumentException("onNotification is null"); @@ -2290,9 +2397,8 @@ public void call(final Throwable throwable) { * the action to invoke when the source {@link Single} calls {@code onSuccess} * @return the source {@link Single} with the side-effecting behavior applied * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final Single doOnSuccess(final Action1 onSuccess) { if (onSuccess == null) { throw new IllegalArgumentException("onSuccess is null"); @@ -2318,9 +2424,8 @@ public final Single doOnSuccess(final Action1 onSuccess) { * the action that gets called when an observer subscribes to this {@code Single} * @return the source {@code Single} modified so as to call this Action when appropriate * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single doOnSubscribe(final Action0 subscribe) { return create(new SingleDoOnSubscribe(onSubscribe, subscribe)); } @@ -2343,9 +2448,8 @@ public final Single doOnSubscribe(final Action0 subscribe) { * the {@link Scheduler} to use for delaying * @return the source Single shifted in time by the specified delay * @see ReactiveX operators documentation: Delay - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single delay(long delay, TimeUnit unit, Scheduler scheduler) { return create(new SingleDelay(onSubscribe, delay, unit, scheduler)); } @@ -2366,9 +2470,8 @@ public final Single delay(long delay, TimeUnit unit, Scheduler scheduler) { * the {@link TimeUnit} in which {@code period} is defined * @return the source Single shifted in time by the specified delay * @see ReactiveX operators documentation: Delay - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single delay(long delay, TimeUnit unit) { return delay(delay, unit, Schedulers.computation()); } @@ -2396,9 +2499,8 @@ public final Single delay(long delay, TimeUnit unit) { * @return a {@link Single} whose {@link Observer}s' subscriptions trigger an invocation of the given * {@link Single} factory function. * @see ReactiveX operators documentation: Defer - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Single defer(final Callable> singleFactory) { return create(new OnSubscribe() { @Override @@ -2432,9 +2534,8 @@ public void call(SingleSubscriber singleSubscriber) { * the action that gets called when this {@link Single} is unsubscribed. * @return the source {@link Single} modified so as to call this Action when appropriate. * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single doOnUnsubscribe(final Action0 action) { return create(new SingleDoOnUnsubscribe(onSubscribe, action)); } @@ -2454,9 +2555,8 @@ public final Single doOnUnsubscribe(final Action0 action) { * @return a {@link Single} that emits the same item or error as the source {@link Single}, then invokes the * {@link Action0} * @see ReactiveX operators documentation: Do - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single doAfterTerminate(Action0 action) { return create(new SingleDoAfterTerminate(this, action)); } @@ -2636,8 +2736,8 @@ public final Single retryWhen(final Func1, ? * the function that will dispose of the resource * @return the Single whose lifetime controls the lifetime of the dependent resource object * @see ReactiveX operators documentation: Using + * @since 1.3 */ - @Beta public static Single using( final Func0 resourceFactory, final Func1> singleFactory, @@ -2671,10 +2771,8 @@ public static Single using( * a terminal event ({@code onComplete} or {@code onError}). * @return the Single whose lifetime controls the lifetime of the dependent resource object * @see ReactiveX operators documentation: Using - * @Experimental The behavior of this can change at any time. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static Single using( final Func0 resourceFactory, final Func1> singleFactory, @@ -2707,9 +2805,8 @@ public static Single using( * to this Single. * @return a Single that delays the subscription to this Single * until the Observable emits an element or completes normally. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public final Single delaySubscription(Observable other) { if (other == null) { throw new NullPointerException(); @@ -2717,6 +2814,52 @@ public final Single delaySubscription(Observable other) { return create(new SingleOnSubscribeDelaySubscriptionOther(this, other)); } + /** + * Returns a Single which makes sure when a subscriber cancels the subscription, + * the dispose is called on the specified scheduler + * @param scheduler the target scheduler where to execute the cancellation + * @return the new Single instance + * @since 1.2.8 - experimental + */ + @Experimental + public final Single unsubscribeOn(final Scheduler scheduler) { + return create(new OnSubscribe() { + @Override + public void call(final SingleSubscriber t) { + final SingleSubscriber single = new SingleSubscriber() { + @Override + public void onSuccess(T value) { + t.onSuccess(value); + } + + @Override + public void onError(Throwable error) { + t.onError(error); + } + }; + + t.add(Subscriptions.create(new Action0() { + @Override + public void call() { + final Scheduler.Worker w = scheduler.createWorker(); + w.schedule(new Action0() { + @Override + public void call() { + try { + single.unsubscribe(); + } finally { + w.unsubscribe(); + } + } + }); + } + })); + + Single.this.subscribe(single); + } + }); + } + // ------------------------------------------------------------------------- // Fluent test support, super handy and reduces test preparation boilerplate // ------------------------------------------------------------------------- @@ -2729,10 +2872,10 @@ public final Single delaySubscription(Observable other) { *

Scheduler:
*
{@code test} does not operate by default on a particular {@link Scheduler}.
*
+ *

History: 1.2.3 - experimental * @return the new AssertableSubscriber instance - * @since 1.2.3 + * @since 1.3 */ - @Experimental public final AssertableSubscriber test() { AssertableSubscriberObservable ts = AssertableSubscriberObservable.create(Long.MAX_VALUE); subscribe(ts); diff --git a/src/main/java/rx/SingleEmitter.java b/src/main/java/rx/SingleEmitter.java index c4f3d11615..53d034a076 100644 --- a/src/main/java/rx/SingleEmitter.java +++ b/src/main/java/rx/SingleEmitter.java @@ -15,7 +15,6 @@ */ package rx; -import rx.annotations.Experimental; import rx.functions.Cancellable; /** @@ -24,11 +23,10 @@ *

* All methods are thread-safe; calling onSuccess or onError twice or one after the other has * no effect. - * @since 1.2.3 - experimental (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) - * + *

History: 1.2.3 - experimental * @param the success value type + * @since 1.3 */ -@Experimental public interface SingleEmitter { /** diff --git a/src/main/java/rx/exceptions/AssemblyStackTraceException.java b/src/main/java/rx/exceptions/AssemblyStackTraceException.java index 8342adc637..c21cb94510 100644 --- a/src/main/java/rx/exceptions/AssemblyStackTraceException.java +++ b/src/main/java/rx/exceptions/AssemblyStackTraceException.java @@ -17,14 +17,13 @@ import java.util.*; -import rx.annotations.Experimental; import rx.plugins.RxJavaHooks; /** * A RuntimeException that is stackless but holds onto a textual * stacktrace from tracking the assembly location of operators. + * @since 1.3 */ -@Experimental public final class AssemblyStackTraceException extends RuntimeException { /** */ diff --git a/src/main/java/rx/exceptions/CompositeException.java b/src/main/java/rx/exceptions/CompositeException.java index cfbfdc2113..855d720e49 100644 --- a/src/main/java/rx/exceptions/CompositeException.java +++ b/src/main/java/rx/exceptions/CompositeException.java @@ -18,8 +18,6 @@ import java.io.*; import java.util.*; -import rx.annotations.Beta; - /** * Represents an exception that is a composite of one or more other exceptions. A {@code CompositeException} * does not modify the structure of any exception it wraps, but at print-time it iterates through the list of @@ -85,8 +83,8 @@ public CompositeException(Collection errors) { /** * Constructs a CompositeException instance with the supplied initial Throwables. * @param errors the array of Throwables + * @since 1.3 */ - @Beta public CompositeException(Throwable... errors) { Set deDupedExceptions = new LinkedHashSet(); List localExceptions = new ArrayList(); diff --git a/src/main/java/rx/exceptions/Exceptions.java b/src/main/java/rx/exceptions/Exceptions.java index 276e563e48..9e5f25393d 100644 --- a/src/main/java/rx/exceptions/Exceptions.java +++ b/src/main/java/rx/exceptions/Exceptions.java @@ -19,7 +19,6 @@ import rx.Observer; import rx.SingleSubscriber; -import rx.annotations.Beta; /** * Utility class with methods to wrap checked exceptions and @@ -182,9 +181,8 @@ public static void throwIfAny(List exceptions) { * @param t the exception * @param o the observer to report to * @param value the value that caused the exception - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static void throwOrReport(Throwable t, Observer o, Object value) { Exceptions.throwIfFatal(t); o.onError(OnErrorThrowable.addValueAsLastCause(t, value)); @@ -196,9 +194,8 @@ public static void throwOrReport(Throwable t, Observer o, Object value) { * @param t the exception * @param o the observer to report to * @param value the value that caused the exception - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static void throwOrReport(Throwable t, SingleSubscriber o, Object value) { Exceptions.throwIfFatal(t); o.onError(OnErrorThrowable.addValueAsLastCause(t, value)); @@ -208,9 +205,8 @@ public static void throwOrReport(Throwable t, SingleSubscriber o, Object valu * Forwards a fatal exception or reports it to the given Observer. * @param t the exception * @param o the observer to report to - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public static void throwOrReport(Throwable t, Observer o) { Exceptions.throwIfFatal(t); o.onError(t); @@ -221,9 +217,8 @@ public static void throwOrReport(Throwable t, Observer o) { * * @param throwable the exception. * @param subscriber the subscriber to report to. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number). + * @since 1.3 */ - @Beta public static void throwOrReport(Throwable throwable, SingleSubscriber subscriber) { Exceptions.throwIfFatal(throwable); subscriber.onError(throwable); diff --git a/src/main/java/rx/functions/Cancellable.java b/src/main/java/rx/functions/Cancellable.java index 1109522d86..7b92b71884 100644 --- a/src/main/java/rx/functions/Cancellable.java +++ b/src/main/java/rx/functions/Cancellable.java @@ -16,12 +16,10 @@ package rx.functions; -import rx.annotations.Experimental; - /** * A functional interface that has a single close method that can throw. + * @since 1.3 */ -@Experimental public interface Cancellable { /** diff --git a/src/main/java/rx/internal/observers/AssertableSubscriberObservable.java b/src/main/java/rx/internal/observers/AssertableSubscriberObservable.java index b9068b04ff..d9081b5a8d 100644 --- a/src/main/java/rx/internal/observers/AssertableSubscriberObservable.java +++ b/src/main/java/rx/internal/observers/AssertableSubscriberObservable.java @@ -20,7 +20,6 @@ import rx.Producer; import rx.Subscriber; -import rx.annotations.Experimental; import rx.functions.Action0; import rx.observers.TestSubscriber; import rx.observers.AssertableSubscriber; @@ -33,8 +32,8 @@ * * @param * the value type + * @since 1.3 */ -@Experimental public class AssertableSubscriberObservable extends Subscriber implements AssertableSubscriber { private final TestSubscriber ts; diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java index f291f0f649..cbac07ded5 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java @@ -20,7 +20,7 @@ import rx.*; import rx.Completable.OnSubscribe; -import rx.subscriptions.SerialSubscription; +import rx.internal.subscriptions.SequentialSubscription; public final class CompletableOnSubscribeConcatArray implements OnSubscribe { final Completable[] sources; @@ -45,17 +45,17 @@ static final class ConcatInnerSubscriber extends AtomicInteger implements Comple int index; - final SerialSubscription sd; + final SequentialSubscription sd; public ConcatInnerSubscriber(CompletableSubscriber actual, Completable[] sources) { this.actual = actual; this.sources = sources; - this.sd = new SerialSubscription(); + this.sd = new SequentialSubscription(); } @Override public void onSubscribe(Subscription d) { - sd.set(d); + sd.replace(d); } @Override diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java index e5a3e95fc7..7506286906 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java @@ -21,7 +21,8 @@ import rx.*; import rx.Completable.OnSubscribe; -import rx.subscriptions.*; +import rx.internal.subscriptions.SequentialSubscription; +import rx.subscriptions.Subscriptions; public final class CompletableOnSubscribeConcatIterable implements OnSubscribe { final Iterable sources; @@ -61,17 +62,17 @@ static final class ConcatInnerSubscriber extends AtomicInteger implements Comple final CompletableSubscriber actual; final Iterator sources; - final SerialSubscription sd; + final SequentialSubscription sd; public ConcatInnerSubscriber(CompletableSubscriber actual, Iterator sources) { this.actual = actual; this.sources = sources; - this.sd = new SerialSubscription(); + this.sd = new SequentialSubscription(); } @Override public void onSubscribe(Subscription d) { - sd.set(d); + sd.replace(d); } @Override diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java index 20477e64fd..0c8f5bafee 100644 --- a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java @@ -21,7 +21,8 @@ import rx.*; import rx.Completable.OnSubscribe; -import rx.internal.util.unsafe.MpscLinkedQueue; +import rx.internal.util.atomic.MpscLinkedAtomicQueue; +import rx.internal.util.unsafe.*; import rx.subscriptions.CompositeSubscription; public final class CompletableOnSubscribeMergeDelayErrorIterable implements OnSubscribe { @@ -53,7 +54,13 @@ public void call(final CompletableSubscriber s) { final AtomicInteger wip = new AtomicInteger(1); - final Queue queue = new MpscLinkedQueue(); + final Queue queue; + + if (UnsafeAccess.isUnsafeAvailable()) { + queue = new MpscLinkedQueue(); + } else { + queue = new MpscLinkedAtomicQueue(); + } for (;;) { if (set.isUnsubscribed()) { diff --git a/src/main/java/rx/internal/operators/EmptyObservableHolder.java b/src/main/java/rx/internal/operators/EmptyObservableHolder.java index e582407bee..e386539c66 100644 --- a/src/main/java/rx/internal/operators/EmptyObservableHolder.java +++ b/src/main/java/rx/internal/operators/EmptyObservableHolder.java @@ -28,7 +28,7 @@ public enum EmptyObservableHolder implements OnSubscribe { ; /** The singleton instance. */ - static final Observable EMPTY = Observable.create(INSTANCE); + static final Observable EMPTY = Observable.unsafeCreate(INSTANCE); /** diff --git a/src/main/java/rx/internal/operators/NeverObservableHolder.java b/src/main/java/rx/internal/operators/NeverObservableHolder.java index 6a80d22e8b..056c08b034 100644 --- a/src/main/java/rx/internal/operators/NeverObservableHolder.java +++ b/src/main/java/rx/internal/operators/NeverObservableHolder.java @@ -28,7 +28,7 @@ public enum NeverObservableHolder implements OnSubscribe { ; /** The singleton instance. */ - static final Observable NEVER = Observable.create(INSTANCE); + static final Observable NEVER = Observable.unsafeCreate(INSTANCE); /** * Returns a type-corrected singleton instance of the never Observable. diff --git a/src/main/java/rx/internal/operators/OnSubscribeFromEmitter.java b/src/main/java/rx/internal/operators/OnSubscribeCreate.java similarity index 97% rename from src/main/java/rx/internal/operators/OnSubscribeFromEmitter.java rename to src/main/java/rx/internal/operators/OnSubscribeCreate.java index 355ae99c1d..497b240ec0 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeFromEmitter.java +++ b/src/main/java/rx/internal/operators/OnSubscribeCreate.java @@ -29,13 +29,13 @@ import rx.plugins.RxJavaHooks; import rx.subscriptions.SerialSubscription; -public final class OnSubscribeFromEmitter implements OnSubscribe { +public final class OnSubscribeCreate implements OnSubscribe { final Action1> Emitter; final Emitter.BackpressureMode backpressure; - public OnSubscribeFromEmitter(Action1> Emitter, Emitter.BackpressureMode backpressure) { + public OnSubscribeCreate(Action1> Emitter, Emitter.BackpressureMode backpressure) { this.Emitter = Emitter; this.backpressure = backpressure; } @@ -268,7 +268,7 @@ public void onError(Throwable e) { @Override void onOverflow() { - onError(new MissingBackpressureException("fromEmitter: could not emit value due to lack of requests")); + onError(new MissingBackpressureException("create: could not emit value due to lack of requests")); } } diff --git a/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java b/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java new file mode 100644 index 0000000000..8b22e740a0 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeFlatMapCompletable.java @@ -0,0 +1,215 @@ +/** + * Copyright 2017 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.operators; + +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.functions.Func1; +import rx.internal.util.ExceptionsUtils; +import rx.plugins.RxJavaHooks; +import rx.subscriptions.CompositeSubscription; + +/** + * Maps upstream values to Completables and merges them, up to a given + * number of them concurrently, optionally delaying errors. + *

History: 1.2.7 - experimental + * @param the upstream value type + * @since 1.3 + */ +public final class OnSubscribeFlatMapCompletable implements Observable.OnSubscribe { + + final Observable source; + + final Func1 mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + public OnSubscribeFlatMapCompletable(Observable source, Func1 mapper, + boolean delayErrors, int maxConcurrency) { + if (mapper == null) { + throw new NullPointerException("mapper is null"); + } + if (maxConcurrency <= 0) { + throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); + } + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + } + + @Override + public void call(Subscriber child) { + FlatMapCompletableSubscriber parent = new FlatMapCompletableSubscriber(child, mapper, delayErrors, maxConcurrency); + child.add(parent); + child.add(parent.set); + source.unsafeSubscribe(parent); + } + + static final class FlatMapCompletableSubscriber extends Subscriber { + + final Subscriber actual; + + final Func1 mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + final AtomicInteger wip; + + final CompositeSubscription set; + + final AtomicReference errors; + + FlatMapCompletableSubscriber(Subscriber actual, Func1 mapper, + boolean delayErrors, int maxConcurrency) { + this.actual = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.wip = new AtomicInteger(1); + this.errors = new AtomicReference(); + this.set = new CompositeSubscription(); + this.request(maxConcurrency != Integer.MAX_VALUE ? maxConcurrency : Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + Completable c; + + try { + c = mapper.call(t); + if (c == null) { + throw new NullPointerException("The mapper returned a null Completable"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(ex); + return; + } + + InnerSubscriber inner = new InnerSubscriber(); + set.add(inner); + wip.getAndIncrement(); + + c.unsafeSubscribe(inner); + } + + @Override + public void onError(Throwable e) { + if (delayErrors) { + ExceptionsUtils.addThrowable(errors, e); + onCompleted(); + } else { + set.unsubscribe(); + if (errors.compareAndSet(null, e)) { + actual.onError(ExceptionsUtils.terminate(errors)); + } else { + RxJavaHooks.onError(e); + } + } + } + + @Override + public void onCompleted() { + done(); + } + + boolean done() { + if (wip.decrementAndGet() == 0) { + Throwable ex = ExceptionsUtils.terminate(errors); + if (ex != null) { + actual.onError(ex); + } else { + actual.onCompleted(); + } + return true; + } + return false; + } + + public void innerError(InnerSubscriber inner, Throwable e) { + set.remove(inner); + if (delayErrors) { + ExceptionsUtils.addThrowable(errors, e); + if (!done() && maxConcurrency != Integer.MAX_VALUE) { + request(1); + } + } else { + set.unsubscribe(); + unsubscribe(); + if (errors.compareAndSet(null, e)) { + actual.onError(ExceptionsUtils.terminate(errors)); + } else { + RxJavaHooks.onError(e); + } + } + } + + public void innerComplete(InnerSubscriber inner) { + set.remove(inner); + if (!done() && maxConcurrency != Integer.MAX_VALUE) { + request(1); + } + } + + final class InnerSubscriber + extends AtomicReference + implements CompletableSubscriber, Subscription { + + private static final long serialVersionUID = -8588259593722659900L; + + @Override + public void unsubscribe() { + Subscription s = getAndSet(this); + if (s != null && s != this) { + s.unsubscribe(); + } + } + + @Override + public boolean isUnsubscribed() { + return get() == this; + } + + @Override + public void onCompleted() { + innerComplete(this); + } + + @Override + public void onError(Throwable e) { + innerError(this, e); + } + + @Override + public void onSubscribe(Subscription d) { + if (!compareAndSet(null, d)) { + d.unsubscribe(); + if (get() != this) { + RxJavaHooks.onError(new IllegalStateException("Subscription already set!")); + } + } + } + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java b/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java new file mode 100644 index 0000000000..190d6acbc0 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeFlatMapSingle.java @@ -0,0 +1,334 @@ +/** + * Copyright 2017 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import java.util.Queue; +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.functions.Func1; +import rx.internal.util.ExceptionsUtils; +import rx.internal.util.atomic.MpscLinkedAtomicQueue; +import rx.internal.util.unsafe.*; +import rx.plugins.RxJavaHooks; +import rx.subscriptions.CompositeSubscription; + +/** + * Maps upstream values to Singles and merges them, up to a given + * number of them concurrently, optionally delaying errors. + *

History: 1.2.7 - experimental + * @param the upstream value type + * @param the inner Singles and result value type + * @since 1.3 + */ +public final class OnSubscribeFlatMapSingle implements Observable.OnSubscribe { + + final Observable source; + + final Func1> mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + public OnSubscribeFlatMapSingle(Observable source, Func1> mapper, + boolean delayErrors, int maxConcurrency) { + if (mapper == null) { + throw new NullPointerException("mapper is null"); + } + if (maxConcurrency <= 0) { + throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); + } + this.source = source; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + } + + @Override + public void call(Subscriber child) { + FlatMapSingleSubscriber parent = new FlatMapSingleSubscriber(child, mapper, delayErrors, maxConcurrency); + child.add(parent.set); + child.add(parent.requested); + child.setProducer(parent.requested); + source.unsafeSubscribe(parent); + } + + static final class FlatMapSingleSubscriber extends Subscriber { + + final Subscriber actual; + + final Func1> mapper; + + final boolean delayErrors; + + final int maxConcurrency; + + final AtomicInteger wip; + + final AtomicInteger active; + + final CompositeSubscription set; + + final AtomicReference errors; + + final Queue queue; + + final Requested requested; + + volatile boolean done; + + volatile boolean cancelled; + + FlatMapSingleSubscriber(Subscriber actual, + Func1> mapper, + boolean delayErrors, int maxConcurrency) { + this.actual = actual; + this.mapper = mapper; + this.delayErrors = delayErrors; + this.maxConcurrency = maxConcurrency; + this.wip = new AtomicInteger(); + this.errors = new AtomicReference(); + this.requested = new Requested(); + this.set = new CompositeSubscription(); + this.active = new AtomicInteger(); + if (UnsafeAccess.isUnsafeAvailable()) { + queue = new MpscLinkedQueue(); + } else { + queue = new MpscLinkedAtomicQueue(); + } + this.request(maxConcurrency != Integer.MAX_VALUE ? maxConcurrency : Long.MAX_VALUE); + } + + @Override + public void onNext(T t) { + Single c; + + try { + c = mapper.call(t); + if (c == null) { + throw new NullPointerException("The mapper returned a null Single"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + onError(ex); + return; + } + + InnerSubscriber inner = new InnerSubscriber(); + set.add(inner); + active.incrementAndGet(); + + c.subscribe(inner); + } + + @Override + public void onError(Throwable e) { + if (delayErrors) { + ExceptionsUtils.addThrowable(errors, e); + } else { + set.unsubscribe(); + if (!errors.compareAndSet(null, e)) { + RxJavaHooks.onError(e); + return; + } + } + done = true; + drain(); + } + + @Override + public void onCompleted() { + done = true; + drain(); + } + + void innerSuccess(InnerSubscriber inner, R value) { + queue.offer(NotificationLite.next(value)); + set.remove(inner); + active.decrementAndGet(); + drain(); + } + + void innerError(InnerSubscriber inner, Throwable e) { + if (delayErrors) { + ExceptionsUtils.addThrowable(errors, e); + set.remove(inner); + if (!done && maxConcurrency != Integer.MAX_VALUE) { + request(1); + } + } else { + set.unsubscribe(); + unsubscribe(); + if (!errors.compareAndSet(null, e)) { + RxJavaHooks.onError(e); + return; + } + done = true; + } + active.decrementAndGet(); + drain(); + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + Subscriber a = actual; + Queue q = queue; + boolean delayError = this.delayErrors; + AtomicInteger act = active; + + for (;;) { + long r = requested.get(); + long e = 0L; + + while (e != r) { + if (cancelled) { + q.clear(); + return; + } + + boolean d = done; + + if (!delayError && d) { + Throwable ex = errors.get(); + if (ex != null) { + q.clear(); + a.onError(ExceptionsUtils.terminate(errors)); + return; + } + } + + Object o = q.poll(); + + boolean empty = o == null; + + if (d && act.get() == 0 && empty) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(ExceptionsUtils.terminate(errors)); + } else { + a.onCompleted(); + } + return; + } + + if (empty) { + break; + } + + a.onNext(NotificationLite.getValue(o)); + + e++; + } + + if (e == r) { + if (cancelled) { + q.clear(); + return; + } + + if (done) { + if (delayError) { + if (act.get() == 0 && q.isEmpty()) { + Throwable ex = errors.get(); + if (ex != null) { + a.onError(ExceptionsUtils.terminate(errors)); + } else { + a.onCompleted(); + } + return; + } + } else { + Throwable ex = errors.get(); + if (ex != null) { + q.clear(); + a.onError(ExceptionsUtils.terminate(errors)); + return; + } + else if (act.get() == 0 && q.isEmpty()) { + a.onCompleted(); + return; + } + } + } + } + + if (e != 0L) { + requested.produced(e); + if (!done && maxConcurrency != Integer.MAX_VALUE) { + request(e); + } + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + final class Requested extends AtomicLong implements Producer, Subscription { + + private static final long serialVersionUID = -887187595446742742L; + + @Override + public void request(long n) { + if (n > 0L) { + BackpressureUtils.getAndAddRequest(this, n); + drain(); + } + } + + void produced(long e) { + BackpressureUtils.produced(this, e); + } + + @Override + public void unsubscribe() { + cancelled = true; + FlatMapSingleSubscriber.this.unsubscribe(); + if (wip.getAndIncrement() == 0) { + queue.clear(); + } + } + + @Override + public boolean isUnsubscribed() { + return cancelled; + } + } + + final class InnerSubscriber extends SingleSubscriber { + + @Override + public void onSuccess(R t) { + innerSuccess(this, t); + } + + @Override + public void onError(Throwable error) { + innerError(this, error); + } + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeFlattenIterable.java b/src/main/java/rx/internal/operators/OnSubscribeFlattenIterable.java index 608e6808b7..fe85e55369 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeFlattenIterable.java +++ b/src/main/java/rx/internal/operators/OnSubscribeFlattenIterable.java @@ -70,9 +70,9 @@ public static Observable createFrom(Observable source, Func1> mapper, int prefetch) { if (source instanceof ScalarSynchronousObservable) { T scalar = ((ScalarSynchronousObservable) source).get(); - return Observable.create(new OnSubscribeScalarFlattenIterable(scalar, mapper)); + return Observable.unsafeCreate(new OnSubscribeScalarFlattenIterable(scalar, mapper)); } - return Observable.create(new OnSubscribeFlattenIterable(source, mapper, prefetch)); + return Observable.unsafeCreate(new OnSubscribeFlattenIterable(source, mapper, prefetch)); } static final class FlattenIterableSubscriber extends Subscriber { diff --git a/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java b/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java index 4666751842..3c99b68262 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java +++ b/src/main/java/rx/internal/operators/OnSubscribeGroupJoin.java @@ -170,7 +170,7 @@ public void onNext(T1 args) { leftMap().put(id, subjSerial); } - Observable window = Observable.create(new WindowObservableFunc(subj, cancel)); + Observable window = Observable.unsafeCreate(new WindowObservableFunc(subj, cancel)); Observable duration = leftDuration.call(args); diff --git a/src/main/java/rx/internal/operators/OnSubscribeRedo.java b/src/main/java/rx/internal/operators/OnSubscribeRedo.java index fc9fe9e4d2..de4f5e4d15 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeRedo.java +++ b/src/main/java/rx/internal/operators/OnSubscribeRedo.java @@ -31,7 +31,7 @@ * limitations under the License. */ -import static rx.Observable.create; // NOPMD +import static rx.Observable.unsafeCreate; // NOPMD import java.util.concurrent.atomic.*; @@ -133,11 +133,11 @@ public static Observable retry(Observable source, final long count) { } public static Observable retry(Observable source, Func1>, ? extends Observable> notificationHandler) { - return create(new OnSubscribeRedo(source, notificationHandler, true, false, Schedulers.trampoline())); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, true, false, Schedulers.trampoline())); } public static Observable retry(Observable source, Func1>, ? extends Observable> notificationHandler, Scheduler scheduler) { - return create(new OnSubscribeRedo(source, notificationHandler, true, false, scheduler)); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, true, false, scheduler)); } public static Observable repeat(Observable source) { @@ -163,15 +163,15 @@ public static Observable repeat(Observable source, final long count, S } public static Observable repeat(Observable source, Func1>, ? extends Observable> notificationHandler) { - return create(new OnSubscribeRedo(source, notificationHandler, false, true, Schedulers.trampoline())); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, false, true, Schedulers.trampoline())); } public static Observable repeat(Observable source, Func1>, ? extends Observable> notificationHandler, Scheduler scheduler) { - return create(new OnSubscribeRedo(source, notificationHandler, false, true, scheduler)); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, false, true, scheduler)); } public static Observable redo(Observable source, Func1>, ? extends Observable> notificationHandler, Scheduler scheduler) { - return create(new OnSubscribeRedo(source, notificationHandler, false, false, scheduler)); + return unsafeCreate(new OnSubscribeRedo(source, notificationHandler, false, false, scheduler)); } private OnSubscribeRedo(Observable source, Func1>, ? extends Observable> f, boolean stopOnComplete, boolean stopOnError, diff --git a/src/main/java/rx/internal/operators/OnSubscribeRefCount.java b/src/main/java/rx/internal/operators/OnSubscribeRefCount.java index c5898ac1b6..4a34663fbb 100644 --- a/src/main/java/rx/internal/operators/OnSubscribeRefCount.java +++ b/src/main/java/rx/internal/operators/OnSubscribeRefCount.java @@ -129,7 +129,13 @@ void cleanup() { // and set the subscriptionCount to 0 lock.lock(); try { + if (baseSubscription == currentBase) { + // backdoor into the ConnectableObservable to cleanup and reset its state + if (source instanceof Subscription) { + ((Subscription)source).unsubscribe(); + } + baseSubscription.unsubscribe(); baseSubscription = new CompositeSubscription(); subscriptionCount.set(0); @@ -148,7 +154,13 @@ public void call() { lock.lock(); try { if (baseSubscription == current) { + if (subscriptionCount.decrementAndGet() == 0) { + // backdoor into the ConnectableObservable to cleanup and reset its state + if (source instanceof Subscription) { + ((Subscription)source).unsubscribe(); + } + baseSubscription.unsubscribe(); // need a new baseSubscription because once // unsubscribed stays that way diff --git a/src/main/java/rx/internal/operators/OperatorSwitchIfEmpty.java b/src/main/java/rx/internal/operators/OnSubscribeSwitchIfEmpty.java similarity index 70% rename from src/main/java/rx/internal/operators/OperatorSwitchIfEmpty.java rename to src/main/java/rx/internal/operators/OnSubscribeSwitchIfEmpty.java index f3ead2c604..db02dfbff9 100644 --- a/src/main/java/rx/internal/operators/OperatorSwitchIfEmpty.java +++ b/src/main/java/rx/internal/operators/OnSubscribeSwitchIfEmpty.java @@ -16,6 +16,8 @@ package rx.internal.operators; +import java.util.concurrent.atomic.AtomicInteger; + import rx.*; import rx.internal.producers.ProducerArbiter; import rx.subscriptions.SerialSubscription; @@ -26,22 +28,28 @@ * empty, the results of the given Observable will be emitted. * @param the value type */ -public final class OperatorSwitchIfEmpty implements Observable.Operator { - private final Observable alternate; +public final class OnSubscribeSwitchIfEmpty implements Observable.OnSubscribe { + + final Observable source; - public OperatorSwitchIfEmpty(Observable alternate) { + final Observable alternate; + + public OnSubscribeSwitchIfEmpty(Observable source, Observable alternate) { + this.source = source; this.alternate = alternate; } @Override - public Subscriber call(Subscriber child) { + public void call(Subscriber child) { final SerialSubscription serial = new SerialSubscription(); ProducerArbiter arbiter = new ProducerArbiter(); final ParentSubscriber parent = new ParentSubscriber(child, serial, arbiter, alternate); + serial.set(parent); child.add(serial); child.setProducer(arbiter); - return parent; + + parent.subscribe(source); } static final class ParentSubscriber extends Subscriber { @@ -52,11 +60,15 @@ static final class ParentSubscriber extends Subscriber { private final ProducerArbiter arbiter; private final Observable alternate; + final AtomicInteger wip; + volatile boolean active; + ParentSubscriber(Subscriber child, final SerialSubscription serial, ProducerArbiter arbiter, Observable alternate) { this.child = child; this.serial = serial; this.arbiter = arbiter; this.alternate = alternate; + this.wip = new AtomicInteger(); } @Override @@ -69,14 +81,33 @@ public void onCompleted() { if (!empty) { child.onCompleted(); } else if (!child.isUnsubscribed()) { - subscribeToAlternate(); + active = false; + subscribe(null); } } - private void subscribeToAlternate() { - AlternateSubscriber as = new AlternateSubscriber(child, arbiter); - serial.set(as); - alternate.unsafeSubscribe(as); + void subscribe(Observable source) { + if (wip.getAndIncrement() == 0) { + do { + if (child.isUnsubscribed()) { + break; + } + + if (!active) { + if (source == null) { + AlternateSubscriber as = new AlternateSubscriber(child, arbiter); + serial.set(as); + active = true; + alternate.unsafeSubscribe(as); + } else { + active = true; + source.unsafeSubscribe(this); + source = null; + } + } + + } while (wip.decrementAndGet() != 0); + } } @Override diff --git a/src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java b/src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java new file mode 100644 index 0000000000..75d75777c8 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeTimeoutSelectorWithFallback.java @@ -0,0 +1,247 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; + +import rx.*; +import rx.exceptions.Exceptions; +import rx.functions.Func1; +import rx.internal.operators.OnSubscribeTimeoutTimedWithFallback.FallbackSubscriber; +import rx.internal.producers.ProducerArbiter; +import rx.internal.subscriptions.SequentialSubscription; +import rx.plugins.RxJavaHooks; + +/** + * Switches to the fallback Observable if: the first upstream item doesn't arrive before + * the first timeout Observable signals an item or completes; or the Observable generated from + * the previous upstream item signals its item or completes before the upstream signals the next item + * of its own. + * + * @param the input and output value type + * @param the value type of the first timeout Observable + * @param the value type of the item-based timeout Observable + * + * @since 1.3.3 + */ +public final class OnSubscribeTimeoutSelectorWithFallback implements Observable.OnSubscribe { + + final Observable source; + + final Observable firstTimeoutIndicator; + + final Func1> itemTimeoutIndicator; + + final Observable fallback; + + public OnSubscribeTimeoutSelectorWithFallback(Observable source, + Observable firstTimeoutIndicator, + Func1> itemTimeoutIndicator, + Observable fallback) { + this.source = source; + this.firstTimeoutIndicator = firstTimeoutIndicator; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.fallback = fallback; + } + + @Override + public void call(Subscriber t) { + TimeoutMainSubscriber parent = new TimeoutMainSubscriber(t, itemTimeoutIndicator, fallback); + t.add(parent.upstream); + t.setProducer(parent.arbiter); + parent.startFirst(firstTimeoutIndicator); + source.subscribe(parent); + } + + static final class TimeoutMainSubscriber extends Subscriber { + + final Subscriber actual; + + final Func1> itemTimeoutIndicator; + + final Observable fallback; + + final ProducerArbiter arbiter; + + final AtomicLong index; + + final SequentialSubscription task; + + final SequentialSubscription upstream; + + long consumed; + + TimeoutMainSubscriber(Subscriber actual, + Func1> itemTimeoutIndicator, + Observable fallback) { + this.actual = actual; + this.itemTimeoutIndicator = itemTimeoutIndicator; + this.fallback = fallback; + this.arbiter = new ProducerArbiter(); + this.index = new AtomicLong(); + this.task = new SequentialSubscription(); + this.upstream = new SequentialSubscription(this); + this.add(task); + } + + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + Subscription s = task.get(); + if (s != null) { + s.unsubscribe(); + } + + actual.onNext(t); + + consumed++; + + Observable timeoutObservable; + + try { + timeoutObservable = itemTimeoutIndicator.call(t); + if (timeoutObservable == null) { + throw new NullPointerException("The itemTimeoutIndicator returned a null Observable"); + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + unsubscribe(); + index.getAndSet(Long.MAX_VALUE); + actual.onError(ex); + return; + } + + TimeoutConsumer tc = new TimeoutConsumer(idx + 1); + if (task.replace(tc)) { + timeoutObservable.subscribe(tc); + } + + } + + void startFirst(Observable firstTimeoutIndicator) { + if (firstTimeoutIndicator != null) { + TimeoutConsumer tc = new TimeoutConsumer(0L); + if (task.replace(tc)) { + firstTimeoutIndicator.subscribe(tc); + } + } + } + + @Override + public void onError(Throwable e) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onError(e); + } else { + RxJavaHooks.onError(e); + } + } + + @Override + public void onCompleted() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onCompleted(); + } + } + + @Override + public void setProducer(Producer p) { + arbiter.setProducer(p); + } + + void onTimeout(long idx) { + if (!index.compareAndSet(idx, Long.MAX_VALUE)) { + return; + } + + unsubscribe(); + + if (fallback == null) { + actual.onError(new TimeoutException()); + } else { + long c = consumed; + if (c != 0L) { + arbiter.produced(c); + } + + FallbackSubscriber fallbackSubscriber = new FallbackSubscriber(actual, arbiter); + + if (upstream.replace(fallbackSubscriber)) { + fallback.subscribe(fallbackSubscriber); + } + } + } + + void onTimeoutError(long idx, Throwable ex) { + if (index.compareAndSet(idx, Long.MAX_VALUE)) { + unsubscribe(); + + actual.onError(ex); + } else { + RxJavaHooks.onError(ex); + } + + } + + final class TimeoutConsumer extends Subscriber { + + final long idx; + + boolean done; + + TimeoutConsumer(long idx) { + this.idx = idx; + } + + @Override + public void onNext(Object t) { + if (!done) { + done = true; + unsubscribe(); + onTimeout(idx); + } + } + + @Override + public void onError(Throwable e) { + if (!done) { + done = true; + onTimeoutError(idx, e); + } else { + RxJavaHooks.onError(e); + } + } + + @Override + public void onCompleted() { + if (!done) { + done = true; + onTimeout(idx); + } + } + } + } +} diff --git a/src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java b/src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java new file mode 100644 index 0000000000..e70c57d667 --- /dev/null +++ b/src/main/java/rx/internal/operators/OnSubscribeTimeoutTimedWithFallback.java @@ -0,0 +1,227 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +import rx.*; +import rx.Scheduler.Worker; +import rx.functions.Action0; +import rx.internal.producers.ProducerArbiter; +import rx.internal.subscriptions.SequentialSubscription; +import rx.plugins.RxJavaHooks; + +/** + * Switches to consuming a fallback Observable if the main source doesn't signal an onNext event + * within the given time frame after subscription or the previous onNext event. + * + * @param the value type + * @since 1.3.3 + */ +public final class OnSubscribeTimeoutTimedWithFallback implements Observable.OnSubscribe { + + final Observable source; + + final long timeout; + + final TimeUnit unit; + + final Scheduler scheduler; + + final Observable fallback; + + public OnSubscribeTimeoutTimedWithFallback(Observable source, long timeout, + TimeUnit unit, Scheduler scheduler, + Observable fallback) { + this.source = source; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.fallback = fallback; + } + + @Override + public void call(Subscriber t) { + TimeoutMainSubscriber parent = new TimeoutMainSubscriber(t, timeout, unit, scheduler.createWorker(), fallback); + t.add(parent.upstream); + t.setProducer(parent.arbiter); + parent.startTimeout(0L); + source.subscribe(parent); + } + + static final class TimeoutMainSubscriber extends Subscriber { + + final Subscriber actual; + + final long timeout; + + final TimeUnit unit; + + final Worker worker; + + final Observable fallback; + + final ProducerArbiter arbiter; + + final AtomicLong index; + + final SequentialSubscription task; + + final SequentialSubscription upstream; + + long consumed; + + TimeoutMainSubscriber(Subscriber actual, long timeout, + TimeUnit unit, Worker worker, + Observable fallback) { + this.actual = actual; + this.timeout = timeout; + this.unit = unit; + this.worker = worker; + this.fallback = fallback; + this.arbiter = new ProducerArbiter(); + this.index = new AtomicLong(); + this.task = new SequentialSubscription(); + this.upstream = new SequentialSubscription(this); + this.add(worker); + this.add(task); + } + + + @Override + public void onNext(T t) { + long idx = index.get(); + if (idx == Long.MAX_VALUE || !index.compareAndSet(idx, idx + 1)) { + return; + } + + Subscription s = task.get(); + if (s != null) { + s.unsubscribe(); + } + + consumed++; + + actual.onNext(t); + + startTimeout(idx + 1); + } + + void startTimeout(long nextIdx) { + task.replace(worker.schedule(new TimeoutTask(nextIdx), timeout, unit)); + } + + @Override + public void onError(Throwable e) { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onError(e); + + worker.unsubscribe(); + } else { + RxJavaHooks.onError(e); + } + } + + @Override + public void onCompleted() { + if (index.getAndSet(Long.MAX_VALUE) != Long.MAX_VALUE) { + task.unsubscribe(); + + actual.onCompleted(); + + worker.unsubscribe(); + } + } + + @Override + public void setProducer(Producer p) { + arbiter.setProducer(p); + } + + void onTimeout(long idx) { + if (!index.compareAndSet(idx, Long.MAX_VALUE)) { + return; + } + + unsubscribe(); + + if (fallback == null) { + actual.onError(new TimeoutException()); + } else { + long c = consumed; + if (c != 0L) { + arbiter.produced(c); + } + + FallbackSubscriber fallbackSubscriber = new FallbackSubscriber(actual, arbiter); + + if (upstream.replace(fallbackSubscriber)) { + fallback.subscribe(fallbackSubscriber); + } + } + } + + final class TimeoutTask implements Action0 { + + final long idx; + + TimeoutTask(long idx) { + this.idx = idx; + } + + @Override + public void call() { + onTimeout(idx); + } + } + } + + static final class FallbackSubscriber extends Subscriber { + + final Subscriber actual; + + final ProducerArbiter arbiter; + + FallbackSubscriber(Subscriber actual, ProducerArbiter arbiter) { + this.actual = actual; + this.arbiter = arbiter; + } + + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable e) { + actual.onError(e); + } + + @Override + public void onCompleted() { + actual.onCompleted(); + } + + @Override + public void setProducer(Producer p) { + arbiter.setProducer(p); + } + } +} diff --git a/src/main/java/rx/internal/operators/OperatorGroupBy.java b/src/main/java/rx/internal/operators/OperatorGroupBy.java index af2fd4347a..5d6ec2f556 100644 --- a/src/main/java/rx/internal/operators/OperatorGroupBy.java +++ b/src/main/java/rx/internal/operators/OperatorGroupBy.java @@ -42,13 +42,16 @@ * the source and group value type * @param * the value type of the groups + * @deprecated + * since 1.3.7, use {@link OperatorGroupByEvicting} instead */ +@Deprecated public final class OperatorGroupBy implements Operator, T> { final Func1 keySelector; final Func1 valueSelector; final int bufferSize; final boolean delayError; - final Func1, Map> mapFactory; + final Func1, Map> mapFactory; //nullable @SuppressWarnings({ "unchecked", "rawtypes" }) public OperatorGroupBy(Func1 keySelector) { @@ -116,6 +119,10 @@ public static final class GroupBySubscriber final int bufferSize; final boolean delayError; final Map> groups; + + // double store the groups to workaround the bug in the + // signature of groupBy with evicting map factory + final Map> groupsCopy; final Queue> queue; final GroupByProducer producer; final Queue evictedKeys; @@ -134,7 +141,7 @@ public static final class GroupBySubscriber volatile boolean done; final AtomicInteger wip; - + public GroupBySubscriber(Subscriber> actual, Func1 keySelector, Func1 valueSelector, int bufferSize, boolean delayError, Func1, Map> mapFactory) { @@ -158,6 +165,7 @@ public GroupBySubscriber(Subscriber> actual, Fun this.evictedKeys = new ConcurrentLinkedQueue(); this.groups = createMap(mapFactory, new EvictionAction(evictedKeys)); } + this.groupsCopy = new ConcurrentHashMap>(); } static class EvictionAction implements Action1 { @@ -202,7 +210,7 @@ public void onNext(T t) { return; } - boolean notNew = true; + boolean newGroup = false; Object mapKey = key != null ? key : NULL_KEY; GroupedUnicast group = groups.get(mapKey); if (group == null) { @@ -211,12 +219,13 @@ public void onNext(T t) { if (!cancelled.get()) { group = GroupedUnicast.createWith(key, bufferSize, this, delayError); groups.put(mapKey, group); + if (evictedKeys != null) { + groupsCopy.put(mapKey, group); + } groupCount.getAndIncrement(); - notNew = false; - q.offer(group); - drain(); + newGroup = true; } else { return; } @@ -236,15 +245,18 @@ public void onNext(T t) { if (evictedKeys != null) { K evictedKey; while ((evictedKey = evictedKeys.poll()) != null) { - GroupedUnicast g = groups.get(evictedKey); + GroupedUnicast g = groupsCopy.remove(evictedKey); + // do a null check on g because cancel(K) could have cleared + // the map if (g != null) { g.onComplete(); } } } - if (notNew) { - s.request(1); + if (newGroup) { + q.offer(group); + drain(); } } @@ -271,6 +283,7 @@ public void onCompleted() { } groups.clear(); if (evictedKeys != null) { + groupsCopy.clear(); evictedKeys.clear(); } @@ -305,6 +318,9 @@ public void cancel(K key) { unsubscribe(); } } + if (evictedKeys != null) { + groupsCopy.remove(mapKey); + } } void drain() { @@ -365,6 +381,7 @@ void errorAll(Subscriber> a, Queue q, Throwab List> list = new ArrayList>(groups.values()); groups.clear(); if (evictedKeys != null) { + groupsCopy.clear(); evictedKeys.clear(); } diff --git a/src/main/java/rx/internal/operators/OperatorGroupByEvicting.java b/src/main/java/rx/internal/operators/OperatorGroupByEvicting.java new file mode 100644 index 0000000000..d02ecf5d39 --- /dev/null +++ b/src/main/java/rx/internal/operators/OperatorGroupByEvicting.java @@ -0,0 +1,605 @@ +/** + * Copyright 2018 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.operators; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.Observable.*; +import rx.exceptions.Exceptions; +import rx.functions.*; +import rx.internal.producers.ProducerArbiter; +import rx.internal.util.*; +import rx.observables.GroupedObservable; +import rx.plugins.RxJavaHooks; +import rx.observers.Subscribers; +import rx.subscriptions.Subscriptions; + +/** + * Groups the items emitted by an Observable according to a specified criterion, and emits these + * grouped items as Observables, one Observable per group. + *

+ * + * + * @param + * the key type + * @param + * the source and group value type + * @param + * the value type of the groups + */ +public final class OperatorGroupByEvicting implements Operator, T>{ + + final Func1 keySelector; + final Func1 valueSelector; + final int bufferSize; + final boolean delayError; + final Func1, Map> mapFactory; //nullable + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorGroupByEvicting(Func1 keySelector) { + this(keySelector, (Func1)UtilityFunctions.identity(), RxRingBuffer.SIZE, false, null); + } + + public OperatorGroupByEvicting(Func1 keySelector, Func1 valueSelector) { + this(keySelector, valueSelector, RxRingBuffer.SIZE, false, null); + } + + public OperatorGroupByEvicting(Func1 keySelector, Func1 valueSelector, int bufferSize, boolean delayError, Func1, Map> mapFactory) { + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + this.mapFactory = mapFactory; + } + + @SuppressWarnings("unchecked") + @Override + public Subscriber call(Subscriber> child) { + Map> groups; + Queue> evictedGroups; + + if (mapFactory == null) { + evictedGroups = null; + groups = new ConcurrentHashMap>(); + } else { + evictedGroups = new ConcurrentLinkedQueue>(); + Action1 evictionAction = (Action1)(Action1) + new EvictionAction(evictedGroups); + try { + groups = (Map>)(Map) + mapFactory.call((Action1)(Action1) evictionAction); + } catch (Throwable ex) { + //Can reach here because mapFactory.call() may throw + Exceptions.throwOrReport(ex, child); + Subscriber parent2 = Subscribers.empty(); + parent2.unsubscribe(); + return parent2; + } + } + final GroupBySubscriber parent = new GroupBySubscriber( + child, keySelector, valueSelector, bufferSize, delayError, groups, evictedGroups); + + child.add(Subscriptions.create(new Action0() { + @Override + public void call() { + parent.cancel(); + } + })); + + child.setProducer(parent.producer); + + return parent; + } + + public static final class GroupByProducer implements Producer { + final GroupBySubscriber parent; + + public GroupByProducer(GroupBySubscriber parent) { + this.parent = parent; + } + @Override + public void request(long n) { + parent.requestMore(n); + } + } + + public static final class GroupBySubscriber + extends Subscriber { + final Subscriber> actual; + final Func1 keySelector; + final Func1 valueSelector; + final int bufferSize; + final boolean delayError; + final Map> groups; + final Queue> queue; + final GroupByProducer producer; + final Queue> evictedGroups; + + static final Object NULL_KEY = new Object(); + + final ProducerArbiter s; + + final AtomicBoolean cancelled; + + final AtomicLong requested; + + final AtomicInteger groupCount; + + Throwable error; + volatile boolean done; + + final AtomicInteger wip; + + public GroupBySubscriber(Subscriber> actual, Func1 keySelector, + Func1 valueSelector, int bufferSize, boolean delayError, Map> groups, + Queue> evictedGroups) { + this.actual = actual; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.bufferSize = bufferSize; + this.delayError = delayError; + this.queue = new ConcurrentLinkedQueue>(); + this.s = new ProducerArbiter(); + this.s.request(bufferSize); + this.producer = new GroupByProducer(this); + this.cancelled = new AtomicBoolean(); + this.requested = new AtomicLong(); + this.groupCount = new AtomicInteger(1); + this.wip = new AtomicInteger(); + this.groups = groups; + this.evictedGroups = evictedGroups; + } + + @Override + public void setProducer(Producer s) { + this.s.setProducer(s); + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + final Queue> q = this.queue; + final Subscriber> a = this.actual; + + K key; + try { + key = keySelector.call(t); + } catch (Throwable ex) { + unsubscribe(); + errorAll(a, q, ex); + return; + } + + boolean newGroup = false; + @SuppressWarnings("unchecked") + K mapKey = key != null ? key : (K) NULL_KEY; + GroupedUnicast group = groups.get(mapKey); + if (group == null) { + // if the main has been cancelled, stop creating groups + // and skip this value + if (!cancelled.get()) { + group = GroupedUnicast.createWith(key, bufferSize, this, delayError); + groups.put(mapKey, group); + + groupCount.getAndIncrement(); + + newGroup = false; + q.offer(group); + drain(); + } else { + return; + } + } + + V v; + try { + v = valueSelector.call(t); + } catch (Throwable ex) { + unsubscribe(); + errorAll(a, q, ex); + return; + } + + group.onNext(v); + + if (evictedGroups != null) { + GroupedUnicast evictedGroup; + while ((evictedGroup = evictedGroups.poll()) != null) { + evictedGroup.onComplete(); + } + } + + if (newGroup) { + q.offer(group); + drain(); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaHooks.onError(t); + return; + } + error = t; + done = true; + groupCount.decrementAndGet(); + drain(); + } + + @Override + public void onCompleted() { + if (done) { + return; + } + + for (GroupedUnicast e : groups.values()) { + e.onComplete(); + } + groups.clear(); + if (evictedGroups != null) { + evictedGroups.clear(); + } + + done = true; + groupCount.decrementAndGet(); + drain(); + } + + public void requestMore(long n) { + if (n < 0) { + throw new IllegalArgumentException("n >= 0 required but it was " + n); + } + + BackpressureUtils.getAndAddRequest(requested, n); + drain(); + } + + public void cancel() { + // cancelling the main source means we don't want any more groups + // but running groups still require new values + if (cancelled.compareAndSet(false, true)) { + if (groupCount.decrementAndGet() == 0) { + unsubscribe(); + } + } + } + + public void cancel(K key) { + Object mapKey = key != null ? key : NULL_KEY; + if (groups.remove(mapKey) != null) { + if (groupCount.decrementAndGet() == 0) { + unsubscribe(); + } + } + } + + void drain() { + if (wip.getAndIncrement() != 0) { + return; + } + + int missed = 1; + + final Queue> q = this.queue; + final Subscriber> a = this.actual; + + for (;;) { + + if (checkTerminated(done, q.isEmpty(), a, q)) { + return; + } + + long r = requested.get(); + boolean unbounded = r == Long.MAX_VALUE; + long e = 0L; + + while (r != 0) { + boolean d = done; + + GroupedObservable t = q.poll(); + + boolean empty = t == null; + + if (checkTerminated(d, empty, a, q)) { + return; + } + + if (empty) { + break; + } + + a.onNext(t); + + r--; + e--; + } + + if (e != 0L) { + if (!unbounded) { + requested.addAndGet(e); + } + s.request(-e); + } + + missed = wip.addAndGet(-missed); + if (missed == 0) { + break; + } + } + } + + void errorAll(Subscriber> a, Queue q, Throwable ex) { + q.clear(); + List> list = new ArrayList>(groups.values()); + groups.clear(); + if (evictedGroups != null) { + evictedGroups.clear(); + } + + for (GroupedUnicast e : list) { + e.onError(ex); + } + + a.onError(ex); + } + + boolean checkTerminated(boolean d, boolean empty, + Subscriber> a, Queue q) { + if (d) { + Throwable err = error; + if (err != null) { + errorAll(a, q, err); + return true; + } else + if (empty) { + actual.onCompleted(); + return true; + } + } + return false; + } + } + + static class EvictionAction implements Action1> { + + final Queue> evictedGroups; + + EvictionAction(Queue> evictedGroups) { + this.evictedGroups = evictedGroups; + } + + @Override + public void call(GroupedUnicast group) { + evictedGroups.offer(group); + } + } + + static final class GroupedUnicast extends GroupedObservable { + + public static GroupedUnicast createWith(K key, int bufferSize, GroupBySubscriber parent, boolean delayError) { + State state = new State(bufferSize, parent, key, delayError); + return new GroupedUnicast(key, state); + } + + final State state; + + protected GroupedUnicast(K key, State state) { + super(key, state); + this.state = state; + } + + public void onNext(T t) { + state.onNext(t); + } + + public void onError(Throwable e) { + state.onError(e); + } + + public void onComplete() { + state.onComplete(); + } + } + + static final class State extends AtomicInteger implements Producer, Subscription, OnSubscribe { + /** */ + private static final long serialVersionUID = -3852313036005250360L; + + final K key; + final Queue queue; + final GroupBySubscriber parent; + final boolean delayError; + + final AtomicLong requested; + + volatile boolean done; + Throwable error; + + final AtomicBoolean cancelled; + + final AtomicReference> actual; + + final AtomicBoolean once; + + + public State(int bufferSize, GroupBySubscriber parent, K key, boolean delayError) { + this.queue = new ConcurrentLinkedQueue(); + this.parent = parent; + this.key = key; + this.delayError = delayError; + this.cancelled = new AtomicBoolean(); + this.actual = new AtomicReference>(); + this.once = new AtomicBoolean(); + this.requested = new AtomicLong(); + } + + @Override + public void request(long n) { + if (n < 0) { + throw new IllegalArgumentException("n >= required but it was " + n); + } + if (n != 0L) { + BackpressureUtils.getAndAddRequest(requested, n); + drain(); + } + } + + @Override + public boolean isUnsubscribed() { + return cancelled.get(); + } + + @Override + public void unsubscribe() { + if (cancelled.compareAndSet(false, true)) { + if (getAndIncrement() == 0) { + parent.cancel(key); + } + } + } + + @Override + public void call(Subscriber s) { + if (once.compareAndSet(false, true)) { + s.add(this); + s.setProducer(this); + actual.lazySet(s); + drain(); + } else { + s.onError(new IllegalStateException("Only one Subscriber allowed!")); + } + } + + public void onNext(T t) { + if (t == null) { + error = new NullPointerException(); + done = true; + } else { + queue.offer(NotificationLite.next(t)); + } + drain(); + } + + public void onError(Throwable e) { + error = e; + done = true; + drain(); + } + + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + int missed = 1; + + final Queue q = queue; + final boolean delayError = this.delayError; + Subscriber a = actual.get(); + for (;;) { + if (a != null) { + if (checkTerminated(done, q.isEmpty(), a, delayError)) { + return; + } + + long r = requested.get(); + boolean unbounded = r == Long.MAX_VALUE; + long e = 0; + + while (r != 0L) { + boolean d = done; + Object v = q.poll(); + boolean empty = v == null; + + if (checkTerminated(d, empty, a, delayError)) { + return; + } + + if (empty) { + break; + } + + a.onNext(NotificationLite.getValue(v)); + + r--; + e--; + } + + if (e != 0L) { + if (!unbounded) { + requested.addAndGet(e); + } + parent.s.request(-e); + } + } + + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + if (a == null) { + a = actual.get(); + } + } + } + + boolean checkTerminated(boolean d, boolean empty, Subscriber a, boolean delayError) { + if (cancelled.get()) { + queue.clear(); + parent.cancel(key); + return true; + } + + if (d) { + if (delayError) { + if (empty) { + Throwable e = error; + if (e != null) { + a.onError(e); + } else { + a.onCompleted(); + } + return true; + } + } else { + Throwable e = error; + if (e != null) { + queue.clear(); + a.onError(e); + return true; + } else + if (empty) { + a.onCompleted(); + return true; + } + } + } + + return false; + } + } +} diff --git a/src/main/java/rx/internal/operators/OperatorMaterialize.java b/src/main/java/rx/internal/operators/OperatorMaterialize.java index c1d4a3b65e..ce9e1be604 100644 --- a/src/main/java/rx/internal/operators/OperatorMaterialize.java +++ b/src/main/java/rx/internal/operators/OperatorMaterialize.java @@ -134,6 +134,7 @@ private void drain() { missed = true; return; } + busy = true; } // drain loop final AtomicLong localRequested = this.requested; diff --git a/src/main/java/rx/internal/operators/OperatorMerge.java b/src/main/java/rx/internal/operators/OperatorMerge.java index fa30f46b10..a52eee07e9 100644 --- a/src/main/java/rx/internal/operators/OperatorMerge.java +++ b/src/main/java/rx/internal/operators/OperatorMerge.java @@ -847,8 +847,11 @@ public void onNext(T t) { } @Override public void onError(Throwable e) { - done = true; + // Need to queue the error first before setting done, so that after emitLoop() removes the subscriber, + // it is guaranteed to notice the error. Otherwise it would be possible that inner subscribers count was 0, + // and at the same time the error queue was empty. parent.getOrCreateErrorQueue().offer(e); + done = true; parent.emit(); } @Override diff --git a/src/main/java/rx/internal/operators/OperatorPublish.java b/src/main/java/rx/internal/operators/OperatorPublish.java index 7d087efcb4..c07d084abe 100644 --- a/src/main/java/rx/internal/operators/OperatorPublish.java +++ b/src/main/java/rx/internal/operators/OperatorPublish.java @@ -123,7 +123,7 @@ public static Observable create(final Observable source, public static Observable create(final Observable source, final Func1, ? extends Observable> selector, final boolean delayError) { - return create(new OnSubscribe() { + return unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber child) { final OnSubscribePublishMulticast op = new OnSubscribePublishMulticast(RxRingBuffer.SIZE, delayError); @@ -155,7 +155,7 @@ public void setProducer(Producer p) { child.add(op); child.add(subscriber); - selector.call(Observable.create(op)).unsafeSubscribe(subscriber); + selector.call(Observable.unsafeCreate(op)).unsafeSubscribe(subscriber); source.unsafeSubscribe(op.subscriber()); } diff --git a/src/main/java/rx/internal/operators/OperatorReplay.java b/src/main/java/rx/internal/operators/OperatorReplay.java index 53511f9c80..6447b2c737 100644 --- a/src/main/java/rx/internal/operators/OperatorReplay.java +++ b/src/main/java/rx/internal/operators/OperatorReplay.java @@ -28,7 +28,7 @@ import rx.schedulers.Timestamped; import rx.subscriptions.Subscriptions; -public final class OperatorReplay extends ConnectableObservable { +public final class OperatorReplay extends ConnectableObservable implements Subscription { /** The source observable. */ final Observable source; /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ @@ -58,7 +58,7 @@ public Object call() { public static Observable multicastSelector( final Func0> connectableFactory, final Func1, ? extends Observable> selector) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber child) { ConnectableObservable co; @@ -254,6 +254,17 @@ private OperatorReplay(OnSubscribe onSubscribe, Observable sourc this.bufferFactory = bufferFactory; } + @Override + public void unsubscribe() { + current.lazySet(null); + } + + @Override + public boolean isUnsubscribed() { + ReplaySubscriber ps = current.get(); + return ps == null || ps.isUnsubscribed(); + } + @Override public void connect(Action1 connection) { boolean doConnect; @@ -1239,9 +1250,18 @@ Node getInitialHead() { Node prev = get(); Node next = prev.get(); - while (next != null && ((Timestamped)next.value).getTimestampMillis() <= timeLimit) { - prev = next; - next = next.get(); + while (next != null) { + Object o = next.value; + Object v = leaveTransform(o); + if (NotificationLite.isCompleted(v) || NotificationLite.isError(v)) { + break; + } + if (((Timestamped)o).getTimestampMillis() <= timeLimit) { + prev = next; + next = next.get(); + } else { + break; + } } return prev; diff --git a/src/main/java/rx/internal/operators/OperatorSubscribeOn.java b/src/main/java/rx/internal/operators/OperatorSubscribeOn.java index 9b90cbc26a..0be1b01829 100644 --- a/src/main/java/rx/internal/operators/OperatorSubscribeOn.java +++ b/src/main/java/rx/internal/operators/OperatorSubscribeOn.java @@ -31,68 +31,92 @@ public final class OperatorSubscribeOn implements OnSubscribe { final Scheduler scheduler; final Observable source; + final boolean requestOn; - public OperatorSubscribeOn(Observable source, Scheduler scheduler) { + public OperatorSubscribeOn(Observable source, Scheduler scheduler, boolean requestOn) { this.scheduler = scheduler; this.source = source; + this.requestOn = requestOn; } @Override public void call(final Subscriber subscriber) { final Worker inner = scheduler.createWorker(); + + SubscribeOnSubscriber parent = new SubscribeOnSubscriber(subscriber, requestOn, inner, source); + subscriber.add(parent); subscriber.add(inner); - inner.schedule(new Action0() { - @Override - public void call() { - final Thread t = Thread.currentThread(); + inner.schedule(parent); + } - Subscriber s = new Subscriber(subscriber) { - @Override - public void onNext(T t) { - subscriber.onNext(t); - } + static final class SubscribeOnSubscriber extends Subscriber implements Action0 { - @Override - public void onError(Throwable e) { - try { - subscriber.onError(e); - } finally { - inner.unsubscribe(); - } - } + final Subscriber actual; - @Override - public void onCompleted() { - try { - subscriber.onCompleted(); - } finally { - inner.unsubscribe(); - } - } + final boolean requestOn; + + final Worker worker; + + Observable source; + + Thread t; + + SubscribeOnSubscriber(Subscriber actual, boolean requestOn, Worker worker, Observable source) { + this.actual = actual; + this.requestOn = requestOn; + this.worker = worker; + this.source = source; + } - @Override - public void setProducer(final Producer p) { - subscriber.setProducer(new Producer() { + @Override + public void onNext(T t) { + actual.onNext(t); + } + + @Override + public void onError(Throwable e) { + try { + actual.onError(e); + } finally { + worker.unsubscribe(); + } + } + + @Override + public void onCompleted() { + try { + actual.onCompleted(); + } finally { + worker.unsubscribe(); + } + } + + @Override + public void call() { + Observable src = source; + source = null; + t = Thread.currentThread(); + src.unsafeSubscribe(this); + } + + @Override + public void setProducer(final Producer p) { + actual.setProducer(new Producer() { + @Override + public void request(final long n) { + if (t == Thread.currentThread() || !requestOn) { + p.request(n); + } else { + worker.schedule(new Action0() { @Override - public void request(final long n) { - if (t == Thread.currentThread()) { - p.request(n); - } else { - inner.schedule(new Action0() { - @Override - public void call() { - p.request(n); - } - }); - } + public void call() { + p.request(n); } }); } - }; - - source.unsafeSubscribe(s); - } - }); + } + }); + } } } \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorTake.java b/src/main/java/rx/internal/operators/OperatorTake.java index 0be75b4f2c..d49f155d9d 100644 --- a/src/main/java/rx/internal/operators/OperatorTake.java +++ b/src/main/java/rx/internal/operators/OperatorTake.java @@ -19,6 +19,7 @@ import rx.*; import rx.Observable.Operator; +import rx.plugins.RxJavaHooks; /** * An {@code Observable} that emits the first {@code num} items emitted by the source {@code Observable}. @@ -66,6 +67,8 @@ public void onError(Throwable e) { } finally { unsubscribe(); } + } else { + RxJavaHooks.onError(e); } } diff --git a/src/main/java/rx/internal/operators/OperatorThrottleFirst.java b/src/main/java/rx/internal/operators/OperatorThrottleFirst.java index fbd3c64d7d..f9093efa39 100644 --- a/src/main/java/rx/internal/operators/OperatorThrottleFirst.java +++ b/src/main/java/rx/internal/operators/OperatorThrottleFirst.java @@ -48,7 +48,7 @@ public void onStart() { @Override public void onNext(T v) { long now = scheduler.now(); - if (lastOnNext == -1 || now - lastOnNext >= timeInMilliseconds) { + if (lastOnNext == -1 || now < lastOnNext || now - lastOnNext >= timeInMilliseconds) { lastOnNext = now; subscriber.onNext(v); } diff --git a/src/main/java/rx/internal/operators/OperatorTimeout.java b/src/main/java/rx/internal/operators/OperatorTimeout.java deleted file mode 100644 index 3c74663e80..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTimeout.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.internal.operators; - -import java.util.concurrent.TimeUnit; - -import rx.*; -import rx.functions.Action0; - -/** - * Applies a timeout policy for each element in the observable sequence, using - * the specified scheduler to run timeout timers. If the next element isn't - * received within the specified timeout duration starting from its predecessor, - * the other observable sequence is used to produce future messages from that - * point on. - * @param the value type - */ -public final class OperatorTimeout extends OperatorTimeoutBase { - - public OperatorTimeout(final long timeout, final TimeUnit timeUnit, Observable other, Scheduler scheduler) { - super(new FirstTimeoutStub() { - - @Override - public Subscription call(final TimeoutSubscriber timeoutSubscriber, final Long seqId, Scheduler.Worker inner) { - return inner.schedule(new Action0() { - @Override - public void call() { - timeoutSubscriber.onTimeout(seqId); - } - }, timeout, timeUnit); - } - }, new TimeoutStub() { - - @Override - public Subscription call(final TimeoutSubscriber timeoutSubscriber, final Long seqId, T value, Scheduler.Worker inner) { - return inner.schedule(new Action0() { - @Override - public void call() { - timeoutSubscriber.onTimeout(seqId); - } - }, timeout, timeUnit); - } - }, other, scheduler); - } -} diff --git a/src/main/java/rx/internal/operators/OperatorTimeoutBase.java b/src/main/java/rx/internal/operators/OperatorTimeoutBase.java deleted file mode 100644 index 435a60e503..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTimeoutBase.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.internal.operators; - -import java.util.concurrent.TimeoutException; - -import rx.*; -import rx.Observable.Operator; -import rx.functions.*; -import rx.internal.producers.ProducerArbiter; -import rx.observers.SerializedSubscriber; -import rx.subscriptions.SerialSubscription; - -class OperatorTimeoutBase implements Operator { - final FirstTimeoutStub firstTimeoutStub; - final TimeoutStub timeoutStub; - final Observable other; - final Scheduler scheduler; - - /** - * Set up the timeout action on the first value. - * - * @param - */ - /* package-private */interface FirstTimeoutStub extends - Func3, Long, Scheduler.Worker, Subscription> { - } - - /** - * Set up the timeout action based on every value - * - * @param - */ - /* package-private */interface TimeoutStub extends - Func4, Long, T, Scheduler.Worker, Subscription> { - } - - /* package-private */OperatorTimeoutBase(FirstTimeoutStub firstTimeoutStub, TimeoutStub timeoutStub, Observable other, Scheduler scheduler) { - this.firstTimeoutStub = firstTimeoutStub; - this.timeoutStub = timeoutStub; - this.other = other; - this.scheduler = scheduler; - } - - @Override - public Subscriber call(Subscriber subscriber) { - Scheduler.Worker inner = scheduler.createWorker(); - subscriber.add(inner); - // Use SynchronizedSubscriber for safe memory access - // as the subscriber will be accessed in the current thread or the - // scheduler or other Observables. - final SerializedSubscriber synchronizedSubscriber = new SerializedSubscriber(subscriber); - - final SerialSubscription serial = new SerialSubscription(); - synchronizedSubscriber.add(serial); - - TimeoutSubscriber timeoutSubscriber = new TimeoutSubscriber(synchronizedSubscriber, timeoutStub, serial, other, inner); - - synchronizedSubscriber.add(timeoutSubscriber); - synchronizedSubscriber.setProducer(timeoutSubscriber.arbiter); - - serial.set(firstTimeoutStub.call(timeoutSubscriber, 0L, inner)); - - return timeoutSubscriber; - } - - /* package-private */static final class TimeoutSubscriber extends - Subscriber { - - final SerialSubscription serial; - - final SerializedSubscriber serializedSubscriber; - - final TimeoutStub timeoutStub; - - final Observable other; - - final Scheduler.Worker inner; - - final ProducerArbiter arbiter; - - /** Guarded by this. */ - boolean terminated; - /** Guarded by this. */ - long actual; - - TimeoutSubscriber( - SerializedSubscriber serializedSubscriber, - TimeoutStub timeoutStub, SerialSubscription serial, - Observable other, - Scheduler.Worker inner) { - this.serializedSubscriber = serializedSubscriber; - this.timeoutStub = timeoutStub; - this.serial = serial; - this.other = other; - this.inner = inner; - this.arbiter = new ProducerArbiter(); - } - - @Override - public void setProducer(Producer p) { - arbiter.setProducer(p); - } - - @Override - public void onNext(T value) { - boolean onNextWins = false; - long a; - synchronized (this) { - if (!terminated) { - a = ++actual; - onNextWins = true; - } else { - a = actual; - } - } - if (onNextWins) { - serializedSubscriber.onNext(value); - serial.set(timeoutStub.call(this, a, value, inner)); - } - } - - @Override - public void onError(Throwable error) { - boolean onErrorWins = false; - synchronized (this) { - if (!terminated) { - terminated = true; - onErrorWins = true; - } - } - if (onErrorWins) { - serial.unsubscribe(); - serializedSubscriber.onError(error); - } - } - - @Override - public void onCompleted() { - boolean onCompletedWins = false; - synchronized (this) { - if (!terminated) { - terminated = true; - onCompletedWins = true; - } - } - if (onCompletedWins) { - serial.unsubscribe(); - serializedSubscriber.onCompleted(); - } - } - - public void onTimeout(long seqId) { - long expected = seqId; - boolean timeoutWins = false; - synchronized (this) { - if (expected == actual && !terminated) { - terminated = true; - timeoutWins = true; - } - } - if (timeoutWins) { - if (other == null) { - serializedSubscriber.onError(new TimeoutException()); - } else { - Subscriber second = new Subscriber() { - @Override - public void onNext(T t) { - serializedSubscriber.onNext(t); - } - - @Override - public void onError(Throwable e) { - serializedSubscriber.onError(e); - } - - @Override - public void onCompleted() { - serializedSubscriber.onCompleted(); - } - - @Override - public void setProducer(Producer p) { - arbiter.setProducer(p); - } - }; - other.unsafeSubscribe(second); - serial.set(second); - } - } - } - } -} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java b/src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java deleted file mode 100644 index 16034dba6c..0000000000 --- a/src/main/java/rx/internal/operators/OperatorTimeoutWithSelector.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Copyright 2014 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.internal.operators; - -import rx.*; -import rx.exceptions.Exceptions; -import rx.functions.*; -import rx.schedulers.Schedulers; -import rx.subscriptions.Subscriptions; - -/** - * Returns an Observable that mirrors the source Observable. If either the first - * item emitted by the source Observable or any subsequent item don't arrive - * within time windows defined by provided Observables, switch to the - * other Observable if provided, or emit a TimeoutException . - * @param the value type of the main Observable - * @param the value type of the first timeout Observable - * @param the value type of the subsequent timeout Observable - */ -public class OperatorTimeoutWithSelector extends - OperatorTimeoutBase { - - public OperatorTimeoutWithSelector( - final Func0> firstTimeoutSelector, - final Func1> timeoutSelector, - Observable other) { - super(new FirstTimeoutStub() { - - @Override - public Subscription call( - final TimeoutSubscriber timeoutSubscriber, - final Long seqId, Scheduler.Worker inner) { - if (firstTimeoutSelector != null) { - Observable o; - try { - o = firstTimeoutSelector.call(); - } catch (Throwable t) { - Exceptions.throwOrReport(t, timeoutSubscriber); - return Subscriptions.unsubscribed(); - } - return o.unsafeSubscribe(new Subscriber() { - - @Override - public void onCompleted() { - timeoutSubscriber.onTimeout(seqId); - } - - @Override - public void onError(Throwable e) { - timeoutSubscriber.onError(e); - } - - @Override - public void onNext(U t) { - timeoutSubscriber.onTimeout(seqId); - } - - }); - } else { - return Subscriptions.unsubscribed(); - } - } - }, new TimeoutStub() { - - @Override - public Subscription call( - final TimeoutSubscriber timeoutSubscriber, - final Long seqId, T value, Scheduler.Worker inner) { - Observable o; - try { - o = timeoutSelector.call(value); - } catch (Throwable t) { - Exceptions.throwOrReport(t, timeoutSubscriber); - return Subscriptions.unsubscribed(); - } - return o.unsafeSubscribe(new Subscriber() { - - @Override - public void onCompleted() { - timeoutSubscriber.onTimeout(seqId); - } - - @Override - public void onError(Throwable e) { - timeoutSubscriber.onError(e); - } - - @Override - public void onNext(V t) { - timeoutSubscriber.onTimeout(seqId); - } - - }); - } - }, other, Schedulers.immediate()); - } - -} diff --git a/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java b/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java index 2f72d62d6b..a581009884 100644 --- a/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java +++ b/src/main/java/rx/internal/operators/OperatorUnsubscribeOn.java @@ -52,6 +52,10 @@ public void onNext(T t) { subscriber.onNext(t); } + @Override + public void setProducer(Producer p) { + subscriber.setProducer(p); + } }; subscriber.add(Subscriptions.create(new Action0() { diff --git a/src/main/java/rx/internal/operators/SingleOperatorCast.java b/src/main/java/rx/internal/operators/SingleOperatorCast.java new file mode 100644 index 0000000000..b77381b161 --- /dev/null +++ b/src/main/java/rx/internal/operators/SingleOperatorCast.java @@ -0,0 +1,37 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.operators; + +import rx.functions.Func1; + +/** + * Converts the element of a Single to the specified type. + * @param the input value type + * @param the output value type + */ +public class SingleOperatorCast implements Func1 { + + final Class castClass; + + public SingleOperatorCast(Class castClass) { + this.castClass = castClass; + } + + @Override + public R call(T t) { + return castClass.cast(t); + } +} diff --git a/src/main/java/rx/internal/schedulers/NewThreadWorker.java b/src/main/java/rx/internal/schedulers/NewThreadWorker.java index dd63eddf9a..23f8af90e2 100644 --- a/src/main/java/rx/internal/schedulers/NewThreadWorker.java +++ b/src/main/java/rx/internal/schedulers/NewThreadWorker.java @@ -16,7 +16,7 @@ package rx.internal.schedulers; import java.lang.reflect.*; -import java.util.Iterator; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; @@ -110,10 +110,12 @@ public static void deregisterExecutor(ScheduledExecutorService service) { } /** Purges each registered executor and eagerly evicts shutdown executors. */ - @SuppressAnimalSniffer // CHM.keySet returns KeySetView in Java 8+; false positive here static void purgeExecutors() { try { - Iterator it = EXECUTORS.keySet().iterator(); + // This prevents map.keySet to compile to a Java 8+ KeySetView return type + // and cause NoSuchMethodError on Java 6-7 runtimes. + Map map = EXECUTORS; + Iterator it = map.keySet().iterator(); while (it.hasNext()) { ScheduledThreadPoolExecutor exec = it.next(); if (!exec.isShutdown()) { diff --git a/src/main/java/rx/internal/schedulers/SchedulePeriodicHelper.java b/src/main/java/rx/internal/schedulers/SchedulePeriodicHelper.java index 4ca7b74941..e11e072c0a 100644 --- a/src/main/java/rx/internal/schedulers/SchedulePeriodicHelper.java +++ b/src/main/java/rx/internal/schedulers/SchedulePeriodicHelper.java @@ -45,7 +45,7 @@ private SchedulePeriodicHelper() { } /** - * Return the current time in nanoseconds. + * Return the current time in nanoseconds. */ public interface NowNanoSupplier { long nowNanos(); @@ -53,7 +53,7 @@ public interface NowNanoSupplier { public static Subscription schedulePeriodically( final Worker worker, - final Action0 action, + final Action0 action, long initialDelay, long period, TimeUnit unit, final NowNanoSupplier nowNanoSupplier) { final long periodInNanos = unit.toNanos(period); diff --git a/src/main/java/rx/internal/schedulers/SchedulerWhen.java b/src/main/java/rx/internal/schedulers/SchedulerWhen.java index 880992c9d5..f35528bb12 100644 --- a/src/main/java/rx/internal/schedulers/SchedulerWhen.java +++ b/src/main/java/rx/internal/schedulers/SchedulerWhen.java @@ -26,7 +26,6 @@ import rx.Observer; import rx.Scheduler; import rx.Subscription; -import rx.annotations.Experimental; import rx.functions.Action0; import rx.functions.Func1; import rx.internal.operators.BufferUntilSubscriber; @@ -101,8 +100,8 @@ * })); * }); * + * @since 1.3 */ -@Experimental public class SchedulerWhen extends Scheduler implements Subscription { private final Scheduler actualScheduler; private final Observer> workerObserver; diff --git a/src/main/java/rx/internal/subscriptions/CancellableSubscription.java b/src/main/java/rx/internal/subscriptions/CancellableSubscription.java index 24c258b333..dcfd1bd075 100644 --- a/src/main/java/rx/internal/subscriptions/CancellableSubscription.java +++ b/src/main/java/rx/internal/subscriptions/CancellableSubscription.java @@ -1,3 +1,19 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package rx.internal.subscriptions; import java.util.concurrent.atomic.AtomicReference; diff --git a/src/main/java/rx/internal/util/BackpressureDrainManager.java b/src/main/java/rx/internal/util/BackpressureDrainManager.java index 9fde1e62f7..da31c78178 100644 --- a/src/main/java/rx/internal/util/BackpressureDrainManager.java +++ b/src/main/java/rx/internal/util/BackpressureDrainManager.java @@ -18,16 +18,14 @@ import java.util.concurrent.atomic.AtomicLong; import rx.Producer; -import rx.annotations.Experimental; /** * Manages the producer-backpressure-consumer interplay by * matching up available elements with requested elements and/or * terminal events. - * - * @since 1.1.0 + *

History: 1.1.0 - experimental + * @since 1.3 */ -@Experimental public final class BackpressureDrainManager extends AtomicLong implements Producer { /** */ private static final long serialVersionUID = 2826241102729529449L; diff --git a/src/main/java/rx/internal/util/BlockingUtils.java b/src/main/java/rx/internal/util/BlockingUtils.java index 17b0143e52..cb010af57e 100644 --- a/src/main/java/rx/internal/util/BlockingUtils.java +++ b/src/main/java/rx/internal/util/BlockingUtils.java @@ -17,7 +17,6 @@ package rx.internal.util; import rx.Subscription; -import rx.annotations.Experimental; import java.util.concurrent.CountDownLatch; @@ -25,8 +24,8 @@ * Utility functions relating to blocking types. *

* Not intended to be part of the public API. + * @since 1.3 */ -@Experimental public final class BlockingUtils { private BlockingUtils() { } @@ -37,7 +36,6 @@ private BlockingUtils() { } * @param latch a CountDownLatch * @param subscription the Subscription to wait on. */ - @Experimental public static void awaitForComplete(CountDownLatch latch, Subscription subscription) { if (latch.getCount() == 0) { // Synchronous observable completes before awaiting for it. diff --git a/src/main/java/rx/internal/util/ScalarSynchronousObservable.java b/src/main/java/rx/internal/util/ScalarSynchronousObservable.java index d14269953c..141030266e 100644 --- a/src/main/java/rx/internal/util/ScalarSynchronousObservable.java +++ b/src/main/java/rx/internal/util/ScalarSynchronousObservable.java @@ -122,7 +122,7 @@ public void call() { }; } - return create(new ScalarAsyncOnSubscribe(t, onSchedule)); + return unsafeCreate(new ScalarAsyncOnSubscribe(t, onSchedule)); } /** The OnSubscribe callback for the Observable constructor. */ @@ -225,7 +225,7 @@ public String toString() { * @return the new observable */ public Observable scalarFlatMap(final Func1> func) { - return create(new OnSubscribe() { + return unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber child) { Observable o = func.call(t); diff --git a/src/main/java/rx/observables/AsyncOnSubscribe.java b/src/main/java/rx/observables/AsyncOnSubscribe.java index 35eb618736..7a1eef7112 100644 --- a/src/main/java/rx/observables/AsyncOnSubscribe.java +++ b/src/main/java/rx/observables/AsyncOnSubscribe.java @@ -23,7 +23,7 @@ import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; -import rx.annotations.Experimental; +import rx.annotations.Beta; import rx.functions.*; import rx.internal.operators.BufferUntilSubscriber; import rx.observers.SerializedObserver; @@ -33,7 +33,7 @@ /** * A utility class to create {@code OnSubscribe} functions that respond correctly to back * pressure requests from subscribers. This is an improvement over - * {@link rx.Observable#create(OnSubscribe) Observable.create(OnSubscribe)} which does not provide + * {@link rx.Observable#unsafeCreate(OnSubscribe) Observable.create(OnSubscribe)} which does not provide * any means of managing back pressure requests out-of-the-box. This variant of an OnSubscribe * function allows for the asynchronous processing of requests. * @@ -43,8 +43,9 @@ * {@link #onUnsubscribe(Object) onUnsubscribe(S)}. * @param * the type of {@code Subscribers} that will be compatible with {@code this}. + * @since 1.3 - beta */ -@Experimental +@Beta public abstract class AsyncOnSubscribe implements OnSubscribe { /** @@ -110,7 +111,6 @@ protected void onUnsubscribe(S state) { * {@link #next(Object, long, Observer) next(S, long, Observer)}) * @return an AsyncOnSubscribe that emits data in a protocol compatible with back-pressure. */ - @Experimental public static AsyncOnSubscribe createSingleState(Func0 generator, final Action3>> next) { Func3>, S> nextFunc = @@ -141,7 +141,6 @@ public S call(S state, Long requested, Observer> subscri * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createSingleState(Func0 generator, final Action3>> next, final Action1 onUnsubscribe) { @@ -171,7 +170,6 @@ public S call(S state, Long requested, Observer> subscri * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createStateful(Func0 generator, Func3>, ? extends S> next, Action1 onUnsubscribe) { @@ -192,7 +190,6 @@ public static AsyncOnSubscribe createStateful(Func0 ge * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createStateful(Func0 generator, Func3>, ? extends S> next) { return new AsyncOnSubscribeImpl(generator, next); @@ -212,7 +209,6 @@ public static AsyncOnSubscribe createStateful(Func0 ge * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createStateless(final Action2>> next) { Func3>, Void> nextFunc = new Func3>, Void>() { @@ -240,7 +236,6 @@ public Void call(Void state, Long requested, Observer> s * @return an AsyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. */ - @Experimental public static AsyncOnSubscribe createStateless(final Action2>> next, final Action0 onUnsubscribe) { Func3>, Void> nextFunc = @@ -538,7 +533,10 @@ boolean tryEmit(long n) { expectedDelivery = n; nextIteration(n); - if (hasTerminated || isUnsubscribed()) { + //hasTerminated will be true when onCompleted was already emitted from the request callback + //even if the the observer has not seen onCompleted from the requested observable, + //so we should not clean up while there are active subscriptions + if (hasTerminated && !subscriptions.hasSubscriptions() || isUnsubscribed()) { cleanup(); return true; } diff --git a/src/main/java/rx/observables/BlockingObservable.java b/src/main/java/rx/observables/BlockingObservable.java index 59cb245415..ebde4486b0 100644 --- a/src/main/java/rx/observables/BlockingObservable.java +++ b/src/main/java/rx/observables/BlockingObservable.java @@ -22,7 +22,6 @@ import rx.*; import rx.Observable; import rx.Observer; -import rx.annotations.Beta; import rx.exceptions.*; import rx.functions.*; import rx.internal.operators.*; @@ -469,9 +468,8 @@ public void onNext(final T item) { /** * Runs the source observable to a terminal event, ignoring any values and rethrowing any exception. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public void subscribe() { final CountDownLatch cdl = new CountDownLatch(1); final Throwable[] error = { null }; @@ -503,9 +501,8 @@ public void onCompleted() { /** * Subscribes to the source and calls back the Observer methods on the current thread. * @param observer the observer to call event methods on - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public void subscribe(Observer observer) { final BlockingQueue queue = new LinkedBlockingQueue(); @@ -548,10 +545,9 @@ public void onCompleted() { *

* The unsubscription and backpressure is composed through. * @param subscriber the subscriber to forward events and calls to in the current thread - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ @SuppressWarnings("unchecked") - @Beta public void subscribe(Subscriber subscriber) { final BlockingQueue queue = new LinkedBlockingQueue(); final Producer[] theProducer = { null }; @@ -631,9 +627,8 @@ public void call() { * * @param onNext the callback action for each source value * @see #forEach(Action1) - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public void subscribe(final Action1 onNext) { subscribe(onNext, new Action1() { @Override @@ -647,9 +642,8 @@ public void call(Throwable t) { * Subscribes to the source and calls the given actions on the current thread. * @param onNext the callback action for each source value * @param onError the callback action for an error event - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Beta public void subscribe(final Action1 onNext, final Action1 onError) { subscribe(onNext, onError, Actions.empty()); } @@ -659,9 +653,8 @@ public void subscribe(final Action1 onNext, final Action1 onNext, final Action1 onError, final Action0 onCompleted) { subscribe(new Observer() { @Override diff --git a/src/main/java/rx/observables/ConnectableObservable.java b/src/main/java/rx/observables/ConnectableObservable.java index 09ac04257d..f1fffceb5f 100644 --- a/src/main/java/rx/observables/ConnectableObservable.java +++ b/src/main/java/rx/observables/ConnectableObservable.java @@ -76,7 +76,7 @@ public void call(Subscription t1) { * @see ReactiveX documentation: RefCount */ public Observable refCount() { - return create(new OnSubscribeRefCount(this)); + return unsafeCreate(new OnSubscribeRefCount(this)); } /** @@ -125,6 +125,6 @@ public Observable autoConnect(int numberOfSubscribers, Action1(this, numberOfSubscribers, connection)); + return unsafeCreate(new OnSubscribeAutoConnect(this, numberOfSubscribers, connection)); } } diff --git a/src/main/java/rx/observables/SyncOnSubscribe.java b/src/main/java/rx/observables/SyncOnSubscribe.java index dc9e2a7a3c..9d4b5245f4 100644 --- a/src/main/java/rx/observables/SyncOnSubscribe.java +++ b/src/main/java/rx/observables/SyncOnSubscribe.java @@ -20,7 +20,6 @@ import rx.*; import rx.Observable.OnSubscribe; -import rx.annotations.Beta; import rx.exceptions.Exceptions; import rx.functions.*; import rx.internal.operators.BackpressureUtils; @@ -29,7 +28,7 @@ /** * A utility class to create {@code OnSubscribe} functions that responds correctly to back * pressure requests from subscribers. This is an improvement over - * {@link rx.Observable#create(OnSubscribe) Observable.create(OnSubscribe)} which does not provide + * {@link rx.Observable#unsafeCreate(OnSubscribe) Observable.create(OnSubscribe)} which does not provide * any means of managing back pressure requests out-of-the-box. * * @param @@ -120,8 +119,8 @@ protected void onUnsubscribe(S state) { * produces data to the downstream subscriber (see {@link #next(Object, Observer) * next(S, Subscriber)}) * @return a SyncOnSubscribe that emits data in a protocol compatible with back-pressure. + * @since 1.3 */ - @Beta public static SyncOnSubscribe createSingleState(Func0 generator, final Action2> next) { Func2, S> nextFunc = new Func2, S>() { @@ -151,8 +150,8 @@ public S call(S state, Observer subscriber) { * clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta public static SyncOnSubscribe createSingleState(Func0 generator, final Action2> next, final Action1 onUnsubscribe) { @@ -181,8 +180,8 @@ public S call(S state, Observer subscriber) { * clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta public static SyncOnSubscribe createStateful(Func0 generator, Func2, ? extends S> next, Action1 onUnsubscribe) { @@ -202,8 +201,8 @@ public static SyncOnSubscribe createStateful(Func0 gen * next(S, Subscriber)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta public static SyncOnSubscribe createStateful(Func0 generator, Func2, ? extends S> next) { return new SyncOnSubscribeImpl(generator, next); @@ -222,8 +221,8 @@ public static SyncOnSubscribe createStateful(Func0 gen * next(S, Subscriber)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta public static SyncOnSubscribe createStateless(final Action1> next) { Func2, Void> nextFunc = new Func2, Void>() { @Override @@ -250,8 +249,8 @@ public Void call(Void state, Observer subscriber) { * clean up behavior (see {@link #onUnsubscribe(Object) onUnsubscribe(S)}) * @return a SyncOnSubscribe that emits data downstream in a protocol compatible with * back-pressure. + * @since 1.3 */ - @Beta public static SyncOnSubscribe createStateless(final Action1> next, final Action0 onUnsubscribe) { Func2, Void> nextFunc = new Func2, Void>() { diff --git a/src/main/java/rx/observers/AssertableSubscriber.java b/src/main/java/rx/observers/AssertableSubscriber.java index 756d3049d0..21180c7eb2 100644 --- a/src/main/java/rx/observers/AssertableSubscriber.java +++ b/src/main/java/rx/observers/AssertableSubscriber.java @@ -19,7 +19,6 @@ import java.util.concurrent.TimeUnit; import rx.*; -import rx.annotations.Experimental; import rx.functions.Action0; /** @@ -30,10 +29,10 @@ *

* This interface extends {@link Observer} and allows injecting onXXX signals into * the testing process. + *

History: 1.2.3 - experimental * @param the value type consumed by this Observer - * @since 1.2.3 + * @since 1.3 */ -@Experimental public interface AssertableSubscriber extends Observer, Subscription { /** diff --git a/src/main/java/rx/observers/AsyncCompletableSubscriber.java b/src/main/java/rx/observers/AsyncCompletableSubscriber.java index 28c346da76..81933b80e6 100644 --- a/src/main/java/rx/observers/AsyncCompletableSubscriber.java +++ b/src/main/java/rx/observers/AsyncCompletableSubscriber.java @@ -19,7 +19,6 @@ import rx.CompletableSubscriber; import rx.Subscription; -import rx.annotations.Experimental; import rx.plugins.RxJavaHooks; /** @@ -54,9 +53,8 @@ * } * } * - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Experimental public abstract class AsyncCompletableSubscriber implements CompletableSubscriber, Subscription { /** * Indicates the unsubscribed state. diff --git a/src/main/java/rx/observers/SafeCompletableSubscriber.java b/src/main/java/rx/observers/SafeCompletableSubscriber.java index bc7b35e858..7430fa371a 100644 --- a/src/main/java/rx/observers/SafeCompletableSubscriber.java +++ b/src/main/java/rx/observers/SafeCompletableSubscriber.java @@ -17,7 +17,6 @@ import rx.CompletableSubscriber; import rx.Subscription; -import rx.annotations.Experimental; import rx.exceptions.*; import rx.plugins.RxJavaHooks; @@ -25,9 +24,8 @@ * Wraps another CompletableSubscriber and handles exceptions thrown * from onError and onCompleted. * - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Experimental public final class SafeCompletableSubscriber implements CompletableSubscriber, Subscription { final CompletableSubscriber actual; @@ -56,8 +54,8 @@ public void onCompleted() { @Override public void onError(Throwable e) { - RxJavaHooks.onError(e); if (done) { + RxJavaHooks.onError(e); return; } done = true; diff --git a/src/main/java/rx/observers/TestSubscriber.java b/src/main/java/rx/observers/TestSubscriber.java index cfa1f74ef9..d35602f572 100644 --- a/src/main/java/rx/observers/TestSubscriber.java +++ b/src/main/java/rx/observers/TestSubscriber.java @@ -20,7 +20,6 @@ import rx.*; import rx.Observer; -import rx.annotations.Experimental; import rx.exceptions.CompositeException; /** @@ -225,9 +224,8 @@ public List> getOnCompletedEvents() { /** * Returns the number of times onCompleted was called on this TestSubscriber. * @return the number of times onCompleted was called on this TestSubscriber. - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final int getCompletions() { return completions; } @@ -337,11 +335,11 @@ private void assertItem(T expected, int i) { if (expected == null) { // check for null equality if (actual != null) { - assertionError("Value at index: " + i + " expected to be [null] but was: [" + actual + "]\n"); + assertionError("Value at index: " + i + " expected: [null] but was: [" + actual + "]\n"); } } else if (!expected.equals(actual)) { assertionError("Value at index: " + i - + " expected to be [" + expected + "] (" + expected.getClass().getSimpleName() + + " expected: [" + expected + "] (" + expected.getClass().getSimpleName() + ") but was: [" + actual + "] (" + (actual != null ? actual.getClass().getSimpleName() : "null") + ")\n"); } @@ -356,9 +354,8 @@ private void assertItem(T expected, int i) { * @param unit the time unit of waiting * @return true if the expected number of onNext events happened * @throws RuntimeException if the sleep is interrupted - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final boolean awaitValueCount(int expected, long timeout, TimeUnit unit) { while (timeout != 0 && valueCount < expected) { try { @@ -696,9 +693,8 @@ final void assertionError(String message) { * * @param expectedFirstValue the expected first value * @param expectedRestValues the optional rest values - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public final void assertValuesAndClear(T expectedFirstValue, T... expectedRestValues) { int n = 1 + expectedRestValues.length; assertValueCount(n); @@ -707,5 +703,6 @@ public final void assertValuesAndClear(T expectedFirstValue, T... expectedRestVa assertItem(expectedRestValues[i], i + 1); } values.clear(); + valueCount = 0; } } diff --git a/src/main/java/rx/plugins/RxJavaCompletableExecutionHook.java b/src/main/java/rx/plugins/RxJavaCompletableExecutionHook.java index c8303ed779..d759d0d021 100644 --- a/src/main/java/rx/plugins/RxJavaCompletableExecutionHook.java +++ b/src/main/java/rx/plugins/RxJavaCompletableExecutionHook.java @@ -16,7 +16,6 @@ package rx.plugins; import rx.*; -import rx.annotations.Experimental; import rx.functions.Func1; /** @@ -35,9 +34,8 @@ * should be fast. If anything time-consuming is to be done it should be spawned asynchronously onto separate * worker threads. * - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Experimental public abstract class RxJavaCompletableExecutionHook { // NOPMD /** * Invoked during the construction by {@link Completable#create(Completable.OnSubscribe)} diff --git a/src/main/java/rx/plugins/RxJavaErrorHandler.java b/src/main/java/rx/plugins/RxJavaErrorHandler.java index 9324cb9062..4c0dfba9d0 100644 --- a/src/main/java/rx/plugins/RxJavaErrorHandler.java +++ b/src/main/java/rx/plugins/RxJavaErrorHandler.java @@ -16,7 +16,6 @@ package rx.plugins; import rx.*; -import rx.annotations.Beta; import rx.exceptions.Exceptions; /** @@ -64,10 +63,8 @@ public void handleError(Throwable e) { * {@code OnErrorThrowable.OnNextValue} * @return a short {@link String} representation of the item if one is known for its type, or null for * default - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the - * release number) + * @since 1.3 */ - @Beta public final String handleOnNextValueRendering(Object item) { try { @@ -95,10 +92,8 @@ public final String handleOnNextValueRendering(Object item) { * @return a short {@link String} representation of the item if one is known for its type, or null for * default * @throws InterruptedException if the rendering thread is interrupted - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the - * release number) + * @since 1.3 */ - @Beta protected String render (Object item) throws InterruptedException { //do nothing by default return null; diff --git a/src/main/java/rx/plugins/RxJavaHooks.java b/src/main/java/rx/plugins/RxJavaHooks.java index 0cac048984..106b9e627f 100644 --- a/src/main/java/rx/plugins/RxJavaHooks.java +++ b/src/main/java/rx/plugins/RxJavaHooks.java @@ -19,7 +19,6 @@ import java.util.concurrent.ScheduledExecutorService; import rx.*; -import rx.annotations.Experimental; import rx.functions.*; import rx.internal.operators.*; @@ -29,8 +28,8 @@ *

* The class features a lockdown state, see {@link #lockdown()} and {@link #isLockdown()}, to * prevent further changes to the hooks. + * @since 1.3 */ -@Experimental public final class RxJavaHooks { /** * Prevents changing the hook callbacks when set to true. diff --git a/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java b/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java index f1b897fa21..47ab6e4095 100644 --- a/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java +++ b/src/main/java/rx/plugins/RxJavaObservableExecutionHook.java @@ -41,7 +41,7 @@ */ public abstract class RxJavaObservableExecutionHook { // NOPMD /** - * Invoked during the construction by {@link Observable#create(OnSubscribe)} + * Invoked during the construction by {@link Observable#unsafeCreate(OnSubscribe)} *

* This can be used to decorate or replace the onSubscribe function or just perform extra * logging, metrics and other such things and pass through the function. diff --git a/src/main/java/rx/plugins/RxJavaPlugins.java b/src/main/java/rx/plugins/RxJavaPlugins.java index 91ba9fa1fa..cac5b167c6 100644 --- a/src/main/java/rx/plugins/RxJavaPlugins.java +++ b/src/main/java/rx/plugins/RxJavaPlugins.java @@ -17,7 +17,6 @@ import java.util.*; import java.util.concurrent.atomic.AtomicReference; -import rx.annotations.Experimental; /** * Registry for plugin implementations that allows global override and handles the retrieval of correct @@ -84,9 +83,9 @@ public static RxJavaPlugins getInstance() { * during application runtime and also bad code could invoke it in * the middle of an application life-cycle and really break applications * if not used cautiously. For more detailed discussions: - * * @see Make RxJavaPlugins.reset() public + * @see Make RxJavaPlugins.reset() public + * @since 1.3 */ - @Experimental public void reset() { INSTANCE.errorHandler.set(null); INSTANCE.observableExecutionHook.set(null); @@ -106,7 +105,7 @@ public void reset() { public RxJavaErrorHandler getErrorHandler() { if (errorHandler.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaErrorHandler.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaErrorHandler.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default errorHandler.compareAndSet(null, DEFAULT_ERROR_HANDLER); @@ -148,7 +147,7 @@ public void registerErrorHandler(RxJavaErrorHandler impl) { public RxJavaObservableExecutionHook getObservableExecutionHook() { if (observableExecutionHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaObservableExecutionHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaObservableExecutionHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default observableExecutionHook.compareAndSet(null, RxJavaObservableExecutionHookDefault.getInstance()); @@ -190,7 +189,7 @@ public void registerObservableExecutionHook(RxJavaObservableExecutionHook impl) public RxJavaSingleExecutionHook getSingleExecutionHook() { if (singleExecutionHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaSingleExecutionHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaSingleExecutionHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default singleExecutionHook.compareAndSet(null, RxJavaSingleExecutionHookDefault.getInstance()); @@ -228,13 +227,12 @@ public void registerSingleExecutionHook(RxJavaSingleExecutionHook impl) { * full class name to load. * * @return {@link RxJavaCompletableExecutionHook} implementation to use - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public RxJavaCompletableExecutionHook getCompletableExecutionHook() { if (completableExecutionHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaCompletableExecutionHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaCompletableExecutionHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default completableExecutionHook.compareAndSet(null, new RxJavaCompletableExecutionHook() { }); @@ -256,15 +254,27 @@ public RxJavaCompletableExecutionHook getCompletableExecutionHook() { * @throws IllegalStateException * if called more than once or after the default was initialized (if usage occurs before trying * to register) - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ - @Experimental public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl) { if (!completableExecutionHook.compareAndSet(null, impl)) { throw new IllegalStateException("Another strategy was already registered: " + singleExecutionHook.get()); } } + /** + * A security manager may prevent accessing the System properties entirely, + * therefore, the SecurityException is turned into an empty properties. + * @return the Properties to use for looking up settings + */ + /* test */ static Properties getSystemPropertiesSafe() { + try { + return System.getProperties(); + } catch (SecurityException ex) { + return new Properties(); + } + } + /* test */ static Object getPluginImplementationViaProperty(Class pluginClass, Properties propsIn) { // Make a defensive clone because traversal may fail with ConcurrentModificationException // if the properties get changed by something outside RxJava. @@ -287,25 +297,32 @@ public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl String classSuffix = ".class"; String implSuffix = ".impl"; - for (Map.Entry e : props.entrySet()) { - String key = e.getKey().toString(); - if (key.startsWith(pluginPrefix) && key.endsWith(classSuffix)) { - String value = e.getValue().toString(); + try { + for (Map.Entry e : props.entrySet()) { + String key = e.getKey().toString(); + if (key.startsWith(pluginPrefix) && key.endsWith(classSuffix)) { + String value = e.getValue().toString(); + + if (classSimpleName.equals(value)) { + String index = key.substring(0, key.length() - classSuffix.length()).substring(pluginPrefix.length()); - if (classSimpleName.equals(value)) { - String index = key.substring(0, key.length() - classSuffix.length()).substring(pluginPrefix.length()); + String implKey = pluginPrefix + index + implSuffix; - String implKey = pluginPrefix + index + implSuffix; + implementingClass = props.getProperty(implKey); - implementingClass = props.getProperty(implKey); + if (implementingClass == null) { + throw new IllegalStateException("Implementing class declaration for " + classSimpleName + " missing: " + implKey); + } - if (implementingClass == null) { - throw new IllegalStateException("Implementing class declaration for " + classSimpleName + " missing: " + implKey); + break; } - - break; } } + } catch (SecurityException ex) { + // https://github.com/ReactiveX/RxJava/issues/5819 + // We don't seem to have access to all properties. + // At least print the exception to the console. + ex.printStackTrace(); } } @@ -342,7 +359,7 @@ public void registerCompletableExecutionHook(RxJavaCompletableExecutionHook impl public RxJavaSchedulersHook getSchedulersHook() { if (schedulersHook.get() == null) { // check for an implementation from System.getProperty first - Object impl = getPluginImplementationViaProperty(RxJavaSchedulersHook.class, System.getProperties()); + Object impl = getPluginImplementationViaProperty(RxJavaSchedulersHook.class, getSystemPropertiesSafe()); if (impl == null) { // nothing set via properties so initialize with default schedulersHook.compareAndSet(null, RxJavaSchedulersHook.getDefaultInstance()); diff --git a/src/main/java/rx/plugins/RxJavaSchedulersHook.java b/src/main/java/rx/plugins/RxJavaSchedulersHook.java index cc1675f322..e65ab1f3bc 100644 --- a/src/main/java/rx/plugins/RxJavaSchedulersHook.java +++ b/src/main/java/rx/plugins/RxJavaSchedulersHook.java @@ -18,7 +18,6 @@ import java.util.concurrent.ThreadFactory; import rx.Scheduler; -import rx.annotations.Experimental; import rx.functions.Action0; import rx.internal.schedulers.CachedThreadScheduler; import rx.internal.schedulers.EventLoopsScheduler; @@ -47,8 +46,8 @@ public class RxJavaSchedulersHook { /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#computation()}. * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createComputationScheduler() { return createComputationScheduler(new RxThreadFactory("RxComputationScheduler-")); } @@ -58,8 +57,8 @@ public static Scheduler createComputationScheduler() { * except using {@code threadFactory} for thread creation. * @param threadFactory the factory to use for each worker thread * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createComputationScheduler(ThreadFactory threadFactory) { if (threadFactory == null) { throw new NullPointerException("threadFactory == null"); @@ -70,8 +69,8 @@ public static Scheduler createComputationScheduler(ThreadFactory threadFactory) /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#io()}. * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createIoScheduler() { return createIoScheduler(new RxThreadFactory("RxIoScheduler-")); } @@ -81,8 +80,8 @@ public static Scheduler createIoScheduler() { * except using {@code threadFactory} for thread creation. * @param threadFactory the factory to use for each worker thread * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createIoScheduler(ThreadFactory threadFactory) { if (threadFactory == null) { throw new NullPointerException("threadFactory == null"); @@ -93,8 +92,8 @@ public static Scheduler createIoScheduler(ThreadFactory threadFactory) { /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#newThread()}. * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createNewThreadScheduler() { return createNewThreadScheduler(new RxThreadFactory("RxNewThreadScheduler-")); } @@ -104,8 +103,8 @@ public static Scheduler createNewThreadScheduler() { * except using {@code threadFactory} for thread creation. * @param threadFactory the factory to use for each worker thread * @return the created Scheduler instance + * @since 1.3 */ - @Experimental public static Scheduler createNewThreadScheduler(ThreadFactory threadFactory) { if (threadFactory == null) { throw new NullPointerException("threadFactory == null"); diff --git a/src/main/java/rx/schedulers/Schedulers.java b/src/main/java/rx/schedulers/Schedulers.java index e6435965e5..1327c6ba93 100644 --- a/src/main/java/rx/schedulers/Schedulers.java +++ b/src/main/java/rx/schedulers/Schedulers.java @@ -19,7 +19,6 @@ import java.util.concurrent.atomic.AtomicReference; import rx.Scheduler; -import rx.annotations.Experimental; import rx.internal.schedulers.*; import rx.plugins.*; @@ -185,8 +184,8 @@ public static Scheduler from(Executor executor) { * Resets the current {@link Schedulers} instance. * This will re-init the cached schedulers on the next usage, * which can be useful in testing. + * @since 1.3 */ - @Experimental public static void reset() { Schedulers s = INSTANCE.getAndSet(null); if (s != null) { diff --git a/src/main/java/rx/schedulers/TestScheduler.java b/src/main/java/rx/schedulers/TestScheduler.java index 89717914c9..0106cdbc67 100644 --- a/src/main/java/rx/schedulers/TestScheduler.java +++ b/src/main/java/rx/schedulers/TestScheduler.java @@ -176,15 +176,15 @@ public void call() { @Override public Subscription schedulePeriodically(Action0 action, long initialDelay, long period, TimeUnit unit) { - return SchedulePeriodicHelper.schedulePeriodically(this, + return SchedulePeriodicHelper.schedulePeriodically(this, action, initialDelay, period, unit, this); } - + @Override public long now() { return TestScheduler.this.now(); } - + @Override public long nowNanos() { return TestScheduler.this.time; diff --git a/src/main/java/rx/singles/BlockingSingle.java b/src/main/java/rx/singles/BlockingSingle.java index 470c012cdb..a113f272ba 100644 --- a/src/main/java/rx/singles/BlockingSingle.java +++ b/src/main/java/rx/singles/BlockingSingle.java @@ -20,7 +20,6 @@ import java.util.concurrent.atomic.AtomicReference; import rx.*; -import rx.annotations.*; import rx.exceptions.Exceptions; import rx.internal.operators.BlockingOperatorToFuture; import rx.internal.util.BlockingUtils; @@ -33,9 +32,8 @@ * or {@link Single#toBlocking()}. * * @param the value type of the sequence - * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) + * @since 1.3 */ -@Beta public final class BlockingSingle { private final Single single; diff --git a/src/main/java/rx/subjects/BehaviorSubject.java b/src/main/java/rx/subjects/BehaviorSubject.java index 551f7f6c23..8a45cf1df9 100644 --- a/src/main/java/rx/subjects/BehaviorSubject.java +++ b/src/main/java/rx/subjects/BehaviorSubject.java @@ -35,14 +35,14 @@ *

*

 {@code
 
-  // observer will receive all events.
+  // observer will receive all 4 events (including "default").
   BehaviorSubject subject = BehaviorSubject.create("default");
   subject.subscribe(observer);
   subject.onNext("one");
   subject.onNext("two");
   subject.onNext("three");
 
-  // observer will receive the "one", "two" and "three" events, but not "zero"
+  // observer will receive the "one", "two" and "three" events, but not "default" and "zero"
   BehaviorSubject subject = BehaviorSubject.create("default");
   subject.onNext("zero");
   subject.onNext("one");
diff --git a/src/main/java/rx/subjects/UnicastSubject.java b/src/main/java/rx/subjects/UnicastSubject.java
index c1c69f9e36..e7addc5cd8 100644
--- a/src/main/java/rx/subjects/UnicastSubject.java
+++ b/src/main/java/rx/subjects/UnicastSubject.java
@@ -19,7 +19,6 @@
 import java.util.concurrent.atomic.*;
 
 import rx.*;
-import rx.annotations.Experimental;
 import rx.exceptions.*;
 import rx.functions.Action0;
 import rx.internal.operators.*;
@@ -32,10 +31,11 @@
  * amount. In this case, the buffered values are no longer retained. If the Subscriber
  * requests a limited amount, queueing is involved and only those values are retained which
  * weren't requested by the Subscriber at that time.
- *
+ * 

+ * * @param the input and output value type + * @since 1.3 */ -@Experimental public final class UnicastSubject extends Subject { final State state; @@ -49,6 +49,7 @@ public final class UnicastSubject extends Subject { public static UnicastSubject create() { return create(16); } + /** * Constructs an empty UnicastSubject instance with a capacity hint. *

The capacity hint determines the internal queue's island size: the larger @@ -59,7 +60,19 @@ public static UnicastSubject create() { * @return the created BufferUntilSubscriber instance */ public static UnicastSubject create(int capacityHint) { - State state = new State(capacityHint, null); + State state = new State(capacityHint, false, null); + return new UnicastSubject(state); + } + + /** + * Constructs an empty UnicastSubject instance with the default capacity hint of 16 elements. + * + * @param delayError deliver pending next events before error. + * @param the input and output value type + * @return the created UnicastSubject instance + */ + public static UnicastSubject create(boolean delayError) { + State state = new State(16, delayError, null); return new UnicastSubject(state); } @@ -78,7 +91,28 @@ public static UnicastSubject create(int capacityHint) { * @return the created BufferUntilSubscriber instance */ public static UnicastSubject create(int capacityHint, Action0 onTerminated) { - State state = new State(capacityHint, onTerminated); + State state = new State(capacityHint, false, onTerminated); + return new UnicastSubject(state); + } + + /** + * Constructs an empty UnicastSubject instance with a capacity hint, delay error + * flag and Action0 instance to call if the subject reaches its terminal state + * or the single Subscriber unsubscribes mid-sequence. + *

The capacity hint determines the internal queue's island size: the larger + * it is the less frequent allocation will happen if there is no subscriber + * or the subscriber hasn't caught up. + * @param the input and output value type + * @param capacityHint the capacity hint for the internal queue + * @param onTerminated the optional callback to call when subject reaches its terminal state + * or the single Subscriber unsubscribes mid-sequence. It will be called + * at most once. + * @param delayError flag indicating whether to deliver pending next events before error. + * @return the created BufferUntilSubscriber instance + */ + public static UnicastSubject create(int capacityHint, + Action0 onTerminated, boolean delayError) { + State state = new State(capacityHint, delayError, onTerminated); return new UnicastSubject(state); } @@ -119,6 +153,8 @@ static final class State extends AtomicLong implements Producer, Observer, final AtomicReference> subscriber; /** The queue holding values until the subscriber arrives and catches up. */ final Queue queue; + /** Deliver pending next events before error. */ + final boolean delayError; /** Atomically set to true on terminal condition. */ final AtomicReference terminateOnce; /** In case the source emitted an error. */ @@ -137,10 +173,12 @@ static final class State extends AtomicLong implements Producer, Observer, * reduce allocation frequency * @param onTerminated the action to call when the subject reaches its terminal state or * the single subscriber unsubscribes. + * @param delayError deliver pending next events before error. */ - public State(int capacityHint, Action0 onTerminated) { + public State(int capacityHint, boolean delayError, Action0 onTerminated) { this.subscriber = new AtomicReference>(); this.terminateOnce = onTerminated != null ? new AtomicReference(onTerminated) : null; + this.delayError = delayError; Queue q; if (capacityHint > 1) { @@ -266,14 +304,14 @@ void replay() { emitting = true; } Queue q = queue; + boolean delayError = this.delayError; for (;;) { Subscriber s = subscriber.get(); boolean unlimited = false; if (s != null) { boolean d = done; boolean empty = q.isEmpty(); - - if (checkTerminated(d, empty, s)) { + if (checkTerminated(d, empty, delayError, s)) { return; } long r = get(); @@ -284,7 +322,7 @@ void replay() { d = done; Object v = q.poll(); empty = v == null; - if (checkTerminated(d, empty, s)) { + if (checkTerminated(d, empty, delayError, s)) { return; } if (empty) { @@ -348,23 +386,28 @@ public boolean isUnsubscribed() { * an error happened or the source terminated and the queue is empty * @param done indicates the source has called onCompleted * @param empty indicates if there are no more source values in the queue + * @param delayError indicates whether to deliver pending next events before error * @param s the target Subscriber to emit events to * @return true if this Subject reached a terminal state and the drain loop should quit */ - boolean checkTerminated(boolean done, boolean empty, Subscriber s) { + boolean checkTerminated(boolean done, boolean empty, boolean delayError, Subscriber s) { if (s.isUnsubscribed()) { queue.clear(); return true; } if (done) { Throwable e = error; - if (e != null) { + if (e != null && !delayError) { queue.clear(); s.onError(e); return true; - } else + } if (empty) { - s.onCompleted(); + if (e != null) { + s.onError(e); + } else { + s.onCompleted(); + } return true; } } diff --git a/src/perf/java/rx/OneItemPerf.java b/src/perf/java/rx/OneItemPerf.java index 43c6539992..7927268c69 100644 --- a/src/perf/java/rx/OneItemPerf.java +++ b/src/perf/java/rx/OneItemPerf.java @@ -70,7 +70,7 @@ public void call(SingleSubscriber t) { @Setup public void setup() { scalar = Observable.just(1); - one = Observable.create(new OnSubscribe() { + one = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.setProducer(new SingleProducer(t, 1)); diff --git a/src/perf/java/rx/jmh/InputWithIncrementingInteger.java b/src/perf/java/rx/jmh/InputWithIncrementingInteger.java index 9700db74c1..24468bb97b 100644 --- a/src/perf/java/rx/jmh/InputWithIncrementingInteger.java +++ b/src/perf/java/rx/jmh/InputWithIncrementingInteger.java @@ -44,7 +44,7 @@ public void setup(final Blackhole bh) { final int size = getSize(); observable = Observable.range(0, size); - firehose = Observable.create(new OnSubscribe() { + firehose = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { diff --git a/src/perf/java/rx/operators/FromComparison.java b/src/perf/java/rx/operators/FromComparison.java index 3ba2d3c751..763a938cae 100644 --- a/src/perf/java/rx/operators/FromComparison.java +++ b/src/perf/java/rx/operators/FromComparison.java @@ -49,8 +49,8 @@ public void setup() { Arrays.fill(array, 1); - iterableSource = Observable.create(new OnSubscribeFromIterable(Arrays.asList(array))); - arraySource = Observable.create(new OnSubscribeFromArray(array)); + iterableSource = Observable.unsafeCreate(new OnSubscribeFromIterable(Arrays.asList(array))); + arraySource = Observable.unsafeCreate(new OnSubscribeFromArray(array)); } @Benchmark diff --git a/src/perf/java/rx/operators/OperatorRangePerf.java b/src/perf/java/rx/operators/OperatorRangePerf.java index 53e06a0c94..78711e3d58 100644 --- a/src/perf/java/rx/operators/OperatorRangePerf.java +++ b/src/perf/java/rx/operators/OperatorRangePerf.java @@ -43,7 +43,7 @@ public static class InputUsingRequest { @Setup public void setup(final Blackhole bh) { - observable = Observable.create(new OnSubscribeRange(0, size)); + observable = Observable.unsafeCreate(new OnSubscribeRange(0, size)); this.bh = bh; } @@ -91,7 +91,7 @@ public static class InputWithoutRequest { @Setup public void setup(final Blackhole bh) { - observable = Observable.create(new OnSubscribeRange(0, size)); + observable = Observable.unsafeCreate(new OnSubscribeRange(0, size)); this.bh = bh; } diff --git a/src/perf/java/rx/operators/OperatorSerializePerf.java b/src/perf/java/rx/operators/OperatorSerializePerf.java index cae310b72c..70b61421ec 100644 --- a/src/perf/java/rx/operators/OperatorSerializePerf.java +++ b/src/perf/java/rx/operators/OperatorSerializePerf.java @@ -59,7 +59,7 @@ public void serializedSingleStream(Input input) throws InterruptedException { @Benchmark public void serializedTwoStreamsHighlyContended(final Input input) throws InterruptedException { LatchedObserver o = input.newLatchedObserver(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -101,7 +101,7 @@ public Integer call(Long t1) { @Benchmark public void serializedTwoStreamsSlightlyContended(final InputWithInterval input) throws InterruptedException { LatchedObserver o = input.newLatchedObserver(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -118,7 +118,7 @@ public void call(Subscriber s) { @Benchmark public void serializedTwoStreamsOneFastOneSlow(final InputWithInterval input) throws InterruptedException { LatchedObserver o = input.newLatchedObserver(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber s) { diff --git a/src/perf/java/rx/operators/OperatorTakeLastOnePerf.java b/src/perf/java/rx/operators/OperatorTakeLastOnePerf.java index 3b54689c53..c96f9a2c66 100644 --- a/src/perf/java/rx/operators/OperatorTakeLastOnePerf.java +++ b/src/perf/java/rx/operators/OperatorTakeLastOnePerf.java @@ -46,7 +46,7 @@ public void takeLastOneUsingTakeLast(Input input) { @Benchmark public void takeLastOneUsingTakeLastOne(Input input) { - Observable.create(new OnSubscribeTakeLastOne(input.observable)).subscribe(input.observer); + Observable.unsafeCreate(new OnSubscribeTakeLastOne(input.observable)).subscribe(input.observer); } } diff --git a/src/test/java/rx/BackpressureTests.java b/src/test/java/rx/BackpressureTests.java index f9931f8754..16a1565b0a 100644 --- a/src/test/java/rx/BackpressureTests.java +++ b/src/test/java/rx/BackpressureTests.java @@ -593,7 +593,7 @@ private static Observable incrementingIntegers(final AtomicInteger coun } private static Observable incrementingIntegers(final AtomicInteger counter, final ConcurrentLinkedQueue threadsSeen) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { final AtomicLong requested = new AtomicLong(); @@ -637,7 +637,7 @@ public void request(long n) { * @return */ private static Observable firehose(final AtomicInteger counter) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { int i; diff --git a/src/test/java/rx/CompletableTest.java b/src/test/java/rx/CompletableTest.java index 97a40439c8..3f34a983ca 100644 --- a/src/test/java/rx/CompletableTest.java +++ b/src/test/java/rx/CompletableTest.java @@ -93,7 +93,7 @@ public void remove() { /** * A class containing a completable instance and counts the number of subscribers. */ - static final class NormalCompletable extends AtomicInteger { + public static final class NormalCompletable extends AtomicInteger { /** */ private static final long serialVersionUID = 7192337844700923752L; @@ -119,7 +119,7 @@ public void assertSubscriptions(int n) { * A class containing a completable instance that emits a TestException and counts * the number of subscribers. */ - static final class ErrorCompletable extends AtomicInteger { + public static final class ErrorCompletable extends AtomicInteger { /** */ private static final long serialVersionUID = 7192337844700923752L; @@ -391,7 +391,7 @@ public void call(CompletableSubscriber cs) { cs.onError(e); } }) - .andThen(Observable.create(new Observable.OnSubscribe() { + .andThen(Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { hasRun.set(true); diff --git a/src/test/java/rx/ConcatTests.java b/src/test/java/rx/ConcatTests.java index 558f8af1dd..d00d812c52 100644 --- a/src/test/java/rx/ConcatTests.java +++ b/src/test/java/rx/ConcatTests.java @@ -149,7 +149,7 @@ public void testConcatCovariance4() { Media media = new Media(); HorrorMovie horrorMovie2 = new HorrorMovie(); - Observable o1 = Observable.create(new OnSubscribe() { + Observable o1 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { diff --git a/src/test/java/rx/EventStream.java b/src/test/java/rx/EventStream.java index 632e649601..91a6482a5c 100644 --- a/src/test/java/rx/EventStream.java +++ b/src/test/java/rx/EventStream.java @@ -32,7 +32,7 @@ private EventStream() { throw new IllegalStateException("No instances!"); } public static Observable getEventStream(final String type, final int numInstances) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber subscriber) { diff --git a/src/test/java/rx/MergeTests.java b/src/test/java/rx/MergeTests.java index 07bba783ea..c42579cc37 100644 --- a/src/test/java/rx/MergeTests.java +++ b/src/test/java/rx/MergeTests.java @@ -80,7 +80,7 @@ public void testMergeCovariance3() { @Test public void testMergeCovariance4() { - Observable o1 = Observable.create(new OnSubscribe() { + Observable o1 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { diff --git a/src/test/java/rx/ObservableTests.java b/src/test/java/rx/ObservableTests.java index bed04b4cdc..1ad7daaa72 100644 --- a/src/test/java/rx/ObservableTests.java +++ b/src/test/java/rx/ObservableTests.java @@ -94,7 +94,7 @@ public void fromArityArgs1() { @Test public void testCreate() { - Observable observable = Observable.create(new OnSubscribe() { + Observable observable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber Observer) { @@ -140,7 +140,7 @@ public void testCountZeroItems() { @Test public void testCountError() { - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber obsv) { obsv.onError(new RuntimeException()); @@ -289,7 +289,7 @@ public void testOnSubscribeFails() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); final RuntimeException re = new RuntimeException("bad impl"); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { @@ -330,7 +330,7 @@ public void testCustomObservableWithErrorInObserverAsynchronous() throws Interru final CountDownLatch latch = new CountDownLatch(1); final AtomicInteger count = new AtomicInteger(); final AtomicReference error = new AtomicReference(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -395,7 +395,7 @@ public void onNext(String v) { public void testCustomObservableWithErrorInObserverSynchronous() { final AtomicInteger count = new AtomicInteger(); final AtomicReference error = new AtomicReference(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { @@ -445,7 +445,7 @@ public void onNext(String v) { public void testCustomObservableWithErrorInObservableSynchronous() { final AtomicInteger count = new AtomicInteger(); final AtomicReference error = new AtomicReference(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { @@ -484,7 +484,7 @@ public void onNext(String v) { @Test public void testPublishLast() throws InterruptedException { final AtomicInteger count = new AtomicInteger(); - ConnectableObservable connectable = Observable.create(new OnSubscribe() { + ConnectableObservable connectable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { count.incrementAndGet(); @@ -522,7 +522,7 @@ public void call(String value) { @Test public void testReplay() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - ConnectableObservable o = Observable.create(new OnSubscribe() { + ConnectableObservable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -577,7 +577,7 @@ public void call(String v) { @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -625,7 +625,7 @@ public void call(String v) { @Test public void testCacheWithCapacity() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -711,7 +711,7 @@ public void call(Object t1) { public void testErrorThrownWithoutErrorHandlerAsynchronous() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference exception = new AtomicReference(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -1109,7 +1109,7 @@ public void testForEachWithNull() { @Test public void nullOnSubscribe() { - Observable source = Observable.create((OnSubscribe)null); + Observable source = Observable.unsafeCreate((OnSubscribe)null); try { source.subscribe(); @@ -1147,7 +1147,7 @@ public void nullSubscriber() { @Test public void testCacheHint() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { diff --git a/src/test/java/rx/SingleTest.java b/src/test/java/rx/SingleTest.java index eefcde2699..9ae7fa0f90 100644 --- a/src/test/java/rx/SingleTest.java +++ b/src/test/java/rx/SingleTest.java @@ -2230,4 +2230,65 @@ public void call(Throwable t) { assertEquals(1, calls[0]); } + + @Test + public void unsubscribeOnSuccess() throws InterruptedException { + final AtomicReference name = new AtomicReference(); + + final CountDownLatch cdl = new CountDownLatch(1); + + TestSubscriber ts = TestSubscriber.create(); + + Single.fromCallable(new Callable() { + @Override + public Integer call() throws Exception { + return 1; + } + }) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + name.set(Thread.currentThread().getName()); + cdl.countDown(); + } + }) + .subscribeOn(Schedulers.io()) + .unsubscribeOn(Schedulers.computation()) + .subscribe(ts); + + cdl.await(); + + ts.awaitTerminalEvent(); + ts.assertReceivedOnNext(Arrays.asList(1)); + + assertTrue(name.get().startsWith("RxComputation")); + } + + @Test + public void unsubscribeOnError() throws InterruptedException { + final AtomicReference name = new AtomicReference(); + + final CountDownLatch cdl = new CountDownLatch(1); + + TestSubscriber ts = TestSubscriber.create(); + + Single.error(new RuntimeException()) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + name.set(Thread.currentThread().getName()); + cdl.countDown(); + } + }) + .subscribeOn(Schedulers.io()) + .unsubscribeOn(Schedulers.computation()) + .subscribe(ts); + + cdl.await(); + + ts.awaitTerminalEvent(); + ts.assertError(RuntimeException.class); + + assertTrue(name.get().startsWith("RxComputation")); + } } diff --git a/src/test/java/rx/SubscriberTest.java b/src/test/java/rx/SubscriberTest.java index 5180966a1a..a57f2b0229 100644 --- a/src/test/java/rx/SubscriberTest.java +++ b/src/test/java/rx/SubscriberTest.java @@ -229,7 +229,7 @@ public void testRequestToObservable() { TestSubscriber ts = new TestSubscriber(); ts.request(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -252,7 +252,7 @@ public void testRequestThroughMap() { TestSubscriber ts = new TestSubscriber(); ts.request(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -282,7 +282,7 @@ public void testRequestThroughTakeThatReducesRequest() { TestSubscriber ts = new TestSubscriber(); ts.request(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -305,7 +305,7 @@ public void testRequestThroughTakeWhereRequestIsSmallerThanTake() { TestSubscriber ts = new TestSubscriber(); ts.request(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { diff --git a/src/test/java/rx/exceptions/ExceptionsTest.java b/src/test/java/rx/exceptions/ExceptionsTest.java index 5fe0f18d69..07bcb644a8 100644 --- a/src/test/java/rx/exceptions/ExceptionsTest.java +++ b/src/test/java/rx/exceptions/ExceptionsTest.java @@ -254,10 +254,10 @@ public void onNext(Integer integer) { @Test(expected = OnErrorFailedException.class) public void testOnErrorExceptionIsThrownFromSubscribe() { - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s1) { - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s2) { throw new IllegalArgumentException("original exception"); @@ -270,10 +270,10 @@ public void call(Subscriber s2) { @Test(expected = OnErrorFailedException.class) public void testOnErrorExceptionIsThrownFromUnsafeSubscribe() { - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s1) { - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s2) { throw new IllegalArgumentException("original exception"); diff --git a/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java b/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java index 45a109e885..69541f712b 100644 --- a/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java +++ b/src/test/java/rx/internal/operators/BlockingOperatorNextTest.java @@ -234,69 +234,80 @@ public void testNextWithCallingHasNextMultipleTimes() { */ @Test public void testNoBufferingOrBlockingOfSequence() throws Throwable { - final CountDownLatch finished = new CountDownLatch(1); - final int COUNT = 30; - final CountDownLatch timeHasPassed = new CountDownLatch(COUNT); - final AtomicBoolean running = new AtomicBoolean(true); - final AtomicInteger count = new AtomicInteger(0); - final Observable obs = Observable.create(new Observable.OnSubscribe() { + int retries = 10; - @Override - public void call(final Subscriber o) { - new Thread(new Runnable() { + for (;;) { + try { + final CountDownLatch finished = new CountDownLatch(1); + final int COUNT = 30; + final CountDownLatch timeHasPassed = new CountDownLatch(COUNT); + final AtomicBoolean running = new AtomicBoolean(true); + final AtomicInteger count = new AtomicInteger(0); + final Observable obs = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override - public void run() { - try { - while (running.get()) { - o.onNext(count.incrementAndGet()); - timeHasPassed.countDown(); + public void call(final Subscriber o) { + new Thread(new Runnable() { + + @Override + public void run() { + try { + while (running.get()) { + o.onNext(count.incrementAndGet()); + timeHasPassed.countDown(); + } + o.onCompleted(); + } catch (Throwable e) { + o.onError(e); + } finally { + finished.countDown(); + } } - o.onCompleted(); - } catch (Throwable e) { - o.onError(e); - } finally { - finished.countDown(); - } + }).start(); } - }).start(); - } - }); + }); - try { - Iterator it = next(obs).iterator(); + try { + Iterator it = next(obs).iterator(); - assertTrue(it.hasNext()); - int a = it.next(); - assertTrue(it.hasNext()); - int b = it.next(); - // we should have a different value - assertTrue("a and b should be different", a != b); + assertTrue(it.hasNext()); + int a = it.next(); + assertTrue(it.hasNext()); + int b = it.next(); + // we should have a different value + assertTrue("a and b should be different", a != b); - // wait for some time (if times out we are blocked somewhere so fail ... set very high for very slow, constrained machines) - timeHasPassed.await(8000, TimeUnit.MILLISECONDS); + // wait for some time (if times out we are blocked somewhere so fail ... set very high for very slow, constrained machines) + timeHasPassed.await(8000, TimeUnit.MILLISECONDS); - assertTrue(it.hasNext()); - int c = it.next(); + assertTrue(it.hasNext()); + int c = it.next(); - assertTrue("c should not just be the next in sequence", c != (b + 1)); - assertTrue("expected that c [" + c + "] is higher than or equal to " + COUNT, c >= COUNT); + assertTrue("c should not just be the next in sequence", c != (b + 1)); + assertTrue("expected that c [" + c + "] is higher than or equal to " + COUNT, c >= COUNT); - assertTrue(it.hasNext()); - int d = it.next(); - assertTrue(d > c); + assertTrue(it.hasNext()); + int d = it.next(); + assertTrue(d > c); - // shut down the thread - running.set(false); + // shut down the thread + running.set(false); - finished.await(); + finished.await(); - assertFalse(it.hasNext()); + assertFalse(it.hasNext()); - System.out.println("a: " + a + " b: " + b + " c: " + c); - } finally { - running.set(false); // don't let the thread spin indefinitely + System.out.println("a: " + a + " b: " + b + " c: " + c); + } finally { + running.set(false); // don't let the thread spin indefinitely + } + return; + } catch (AssertionError ex) { + if (retries-- == 0) { + throw ex; + } + } } } diff --git a/src/test/java/rx/internal/operators/BlockingOperatorToFutureTest.java b/src/test/java/rx/internal/operators/BlockingOperatorToFutureTest.java index 97d8cdb250..81f54dcdca 100644 --- a/src/test/java/rx/internal/operators/BlockingOperatorToFutureTest.java +++ b/src/test/java/rx/internal/operators/BlockingOperatorToFutureTest.java @@ -65,7 +65,7 @@ public void testExceptionWithMoreThanOneElement() throws Throwable { @Test public void testToFutureWithException() { - Observable obs = Observable.create(new OnSubscribe() { + Observable obs = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { @@ -85,7 +85,7 @@ public void call(Subscriber observer) { @Test(expected = CancellationException.class) public void testGetAfterCancel() throws Exception { - Observable obs = Observable.create(new OperationNeverComplete()); + Observable obs = Observable.unsafeCreate(new OperationNeverComplete()); Future f = toFuture(obs); boolean cancelled = f.cancel(true); assertTrue(cancelled); // because OperationNeverComplete never does @@ -94,7 +94,7 @@ public void testGetAfterCancel() throws Exception { @Test(expected = CancellationException.class) public void testGetWithTimeoutAfterCancel() throws Exception { - Observable obs = Observable.create(new OperationNeverComplete()); + Observable obs = Observable.unsafeCreate(new OperationNeverComplete()); Future f = toFuture(obs); boolean cancelled = f.cancel(true); assertTrue(cancelled); // because OperationNeverComplete never does diff --git a/src/test/java/rx/internal/operators/BlockingOperatorToIteratorTest.java b/src/test/java/rx/internal/operators/BlockingOperatorToIteratorTest.java index e641227f01..91902b2c77 100644 --- a/src/test/java/rx/internal/operators/BlockingOperatorToIteratorTest.java +++ b/src/test/java/rx/internal/operators/BlockingOperatorToIteratorTest.java @@ -55,7 +55,7 @@ public void testToIterator() { @Test(expected = TestException.class) public void testToIteratorWithException() { - Observable obs = Observable.create(new OnSubscribe() { + Observable obs = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { @@ -75,7 +75,7 @@ public void call(Subscriber observer) { @Test(expected = TestException.class) public void testExceptionThrownFromOnSubscribe() { - Iterable strings = Observable.create(new Observable.OnSubscribe() { + Iterable strings = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber subscriber) { throw new TestException("intentional"); diff --git a/src/test/java/rx/internal/operators/CachedObservableTest.java b/src/test/java/rx/internal/operators/CachedObservableTest.java index a642d80b03..40fe2e56de 100644 --- a/src/test/java/rx/internal/operators/CachedObservableTest.java +++ b/src/test/java/rx/internal/operators/CachedObservableTest.java @@ -85,7 +85,7 @@ public void testColdReplayBackpressure() { @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(new Observable.OnSubscribe() { + Observable o = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -220,7 +220,7 @@ public void testAsyncComeAndGo() { @Test public void testNoMissingBackpressureException() { final int m = 4 * 1000 * 1000; - Observable firehose = Observable.create(new OnSubscribe() { + Observable firehose = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { for (int i = 0; i < m; i++) { diff --git a/src/test/java/rx/internal/operators/CompletableConcatTest.java b/src/test/java/rx/internal/operators/CompletableConcatTest.java index 7ce2529322..f55898193c 100644 --- a/src/test/java/rx/internal/operators/CompletableConcatTest.java +++ b/src/test/java/rx/internal/operators/CompletableConcatTest.java @@ -1,6 +1,26 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package rx.internal.operators; -import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertFalse; + +import java.util.Arrays; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.*; @@ -42,4 +62,77 @@ public void call() { Assert.assertEquals(5, calls[0]); } + + @Test + public void andThenNoInterrupt() throws InterruptedException { + for (int k = 0; k < 100; k++) { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + final AtomicBoolean interrupted = new AtomicBoolean(); + + for (int i = 0; i < count; i++) { + Completable.complete() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .andThen(Completable.fromAction(new Action0() { + @Override + public void call() { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + System.out.println("Interrupted! " + Thread.currentThread()); + interrupted.set(true); + } + } + })) + .subscribe(new Action0() { + @Override + public void call() { + latch.countDown(); + } + }); + } + + latch.await(); + assertFalse("The second Completable was interrupted!", interrupted.get()); + } + } + + @Test + public void noInterrupt() throws InterruptedException { + for (int k = 0; k < 100; k++) { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + final AtomicBoolean interrupted = new AtomicBoolean(); + + for (int i = 0; i < count; i++) { + Completable c0 = Completable.fromAction(new Action0() { + @Override + public void call() { + try { + Thread.sleep(30); + } catch (InterruptedException e) { + System.out.println("Interrupted! " + Thread.currentThread()); + interrupted.set(true); + } + } + }); + Completable.concat(Arrays.asList(Completable.complete() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()), + c0) + ) + .subscribe(new Action0() { + @Override + public void call() { + latch.countDown(); + } + }); + } + + latch.await(); + assertFalse("The second Completable was interrupted!", interrupted.get()); + } + } + } diff --git a/src/test/java/rx/internal/operators/CompletableMergeTest.java b/src/test/java/rx/internal/operators/CompletableMergeTest.java index 790d51d5f4..a16518ab36 100644 --- a/src/test/java/rx/internal/operators/CompletableMergeTest.java +++ b/src/test/java/rx/internal/operators/CompletableMergeTest.java @@ -1,3 +1,19 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package rx.internal.operators; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java b/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java new file mode 100644 index 0000000000..31cbff9df1 --- /dev/null +++ b/src/test/java/rx/internal/operators/CompletableOnErrorXTest.java @@ -0,0 +1,63 @@ +/** + * Copyright 2016 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import static org.junit.Assert.*; +import org.junit.Test; + +import rx.Completable; +import rx.functions.Func1; +import rx.observers.AssertableSubscriber; +import rx.subjects.PublishSubject; + +public class CompletableOnErrorXTest { + + @Test + public void nextUnsubscribe() { + PublishSubject ps = PublishSubject.create(); + + AssertableSubscriber as = ps.toCompletable() + .onErrorResumeNext(new Func1() { + @Override + public Completable call(Throwable e) { + return Completable.complete(); + } + }) + .test(); + + assertTrue(ps.hasObservers()); + + as.unsubscribe(); + + assertFalse("Still subscribed!", ps.hasObservers()); + } + + @Test + public void completeUnsubscribe() { + PublishSubject ps = PublishSubject.create(); + + AssertableSubscriber as = ps.toCompletable() + .onErrorComplete() + .test(); + + assertTrue(ps.hasObservers()); + + as.unsubscribe(); + + assertFalse("Still subscribed!", ps.hasObservers()); + } +} diff --git a/src/test/java/rx/internal/operators/OnSubscribeAmbTest.java b/src/test/java/rx/internal/operators/OnSubscribeAmbTest.java index c3e7480fa7..7f9bf0ff49 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeAmbTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeAmbTest.java @@ -51,7 +51,7 @@ public void setUp() { private Observable createObservable(final String[] values, final long interval, final Throwable e) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber subscriber) { @@ -90,7 +90,7 @@ public void testAmb() { Observable observable3 = createObservable(new String[] { "3", "33", "333", "3333" }, 3000, null); - Observable o = Observable.create(amb(observable1, + Observable o = Observable.unsafeCreate(amb(observable1, observable2, observable3)); @SuppressWarnings("unchecked") @@ -119,7 +119,7 @@ public void testAmb2() { Observable observable3 = createObservable(new String[] {}, 3000, new IOException("fake exception")); - Observable o = Observable.create(amb(observable1, + Observable o = Observable.unsafeCreate(amb(observable1, observable2, observable3)); @SuppressWarnings("unchecked") @@ -146,7 +146,7 @@ public void testAmb3() { Observable observable3 = createObservable(new String[] { "3" }, 3000, null); - Observable o = Observable.create(amb(observable1, + Observable o = Observable.unsafeCreate(amb(observable1, observable2, observable3)); @SuppressWarnings("unchecked") @@ -165,7 +165,7 @@ public void testProducerRequestThroughAmb() { ts.requestMore(3); final AtomicLong requested1 = new AtomicLong(); final AtomicLong requested2 = new AtomicLong(); - Observable o1 = Observable.create(new OnSubscribe() { + Observable o1 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -181,7 +181,7 @@ public void request(long n) { } }); - Observable o2 = Observable.create(new OnSubscribe() { + Observable o2 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { diff --git a/src/test/java/rx/internal/operators/OnSubscribeCollectTest.java b/src/test/java/rx/internal/operators/OnSubscribeCollectTest.java index 859ecd63c9..75d6b51954 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeCollectTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeCollectTest.java @@ -130,7 +130,7 @@ public void call(Throwable t) { final RuntimeException e1 = new RuntimeException(); final RuntimeException e2 = new RuntimeException(); TestSubscriber> ts = TestSubscriber.create(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -171,7 +171,7 @@ public void call(List t1, Integer t2) { public void testCollectorFailureDoesNotResultInErrorAndCompletedEmissions() { final RuntimeException e1 = new RuntimeException(); TestSubscriber> ts = TestSubscriber.create(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -209,7 +209,7 @@ public void testCollectorFailureDoesNotResultInErrorAndOnNextEmissions() { final RuntimeException e1 = new RuntimeException(); TestSubscriber> ts = TestSubscriber.create(); final AtomicBoolean added = new AtomicBoolean(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { diff --git a/src/test/java/rx/internal/operators/OnSubscribeFromEmitterTest.java b/src/test/java/rx/internal/operators/OnSubscribeCreateTest.java similarity index 80% rename from src/test/java/rx/internal/operators/OnSubscribeFromEmitterTest.java rename to src/test/java/rx/internal/operators/OnSubscribeCreateTest.java index 8298625a9b..2d8e5f843c 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeFromEmitterTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeCreateTest.java @@ -32,7 +32,7 @@ import rx.plugins.RxJavaHooks; import rx.subjects.PublishSubject; -public class OnSubscribeFromEmitterTest { +public class OnSubscribeCreateTest { PublishEmitter source; @@ -49,7 +49,7 @@ public void before() { @Test public void normalBuffered() { - Observable.fromEmitter(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); source.onNext(1); source.onNext(2); @@ -70,7 +70,7 @@ public void normalBuffered() { @Test public void normalDrop() { - Observable.fromEmitter(source, Emitter.BackpressureMode.DROP).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.DROP).subscribe(ts); source.onNext(1); @@ -86,7 +86,7 @@ public void normalDrop() { @Test public void normalLatest() { - Observable.fromEmitter(source, Emitter.BackpressureMode.LATEST).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.LATEST).subscribe(ts); source.onNext(1); @@ -102,7 +102,7 @@ public void normalLatest() { @Test public void normalNone() { - Observable.fromEmitter(source, Emitter.BackpressureMode.NONE).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.NONE).subscribe(ts); source.onNext(1); source.onNext(2); @@ -115,7 +115,7 @@ public void normalNone() { @Test public void normalNoneRequested() { - Observable.fromEmitter(source, Emitter.BackpressureMode.NONE).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.NONE).subscribe(ts); ts.requestMore(2); source.onNext(1); @@ -130,7 +130,7 @@ public void normalNoneRequested() { @Test public void normalError() { - Observable.fromEmitter(source, Emitter.BackpressureMode.ERROR).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.ERROR).subscribe(ts); source.onNext(1); source.onNext(2); @@ -140,7 +140,7 @@ public void normalError() { ts.assertError(MissingBackpressureException.class); ts.assertNotCompleted(); - Assert.assertEquals("fromEmitter: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); + Assert.assertEquals("create: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); } @Test @@ -153,13 +153,13 @@ public void call(Emitter emitter) { //don't check for unsubscription emitter.onNext(2); }}; - Observable.fromEmitter(source, Emitter.BackpressureMode.ERROR).unsafeSubscribe(ts); + Observable.create(source, Emitter.BackpressureMode.ERROR).unsafeSubscribe(ts); ts.assertNoValues(); ts.assertError(MissingBackpressureException.class); ts.assertNotCompleted(); - Assert.assertEquals("fromEmitter: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); + Assert.assertEquals("create: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); } @Test @@ -172,13 +172,13 @@ public void call(Emitter emitter) { //don't check for unsubscription emitter.onCompleted(); }}; - Observable.fromEmitter(source, Emitter.BackpressureMode.ERROR).unsafeSubscribe(ts); + Observable.create(source, Emitter.BackpressureMode.ERROR).unsafeSubscribe(ts); ts.assertNoValues(); ts.assertError(MissingBackpressureException.class); ts.assertNotCompleted(); - Assert.assertEquals("fromEmitter: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); + Assert.assertEquals("create: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); } @Test @@ -199,13 +199,13 @@ public void call(Emitter emitter) { //don't check for unsubscription emitter.onError(e); }}; - Observable.fromEmitter(source, Emitter.BackpressureMode.ERROR).unsafeSubscribe(ts); + Observable.create(source, Emitter.BackpressureMode.ERROR).unsafeSubscribe(ts); ts.assertNoValues(); ts.assertError(MissingBackpressureException.class); ts.assertNotCompleted(); - Assert.assertEquals("fromEmitter: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); + Assert.assertEquals("create: could not emit value due to lack of requests", ts.getOnErrorEvents().get(0).getMessage()); assertEquals(Arrays.asList(e), list); } finally { RxJavaHooks.reset(); @@ -214,7 +214,7 @@ public void call(Emitter emitter) { @Test public void errorBuffered() { - Observable.fromEmitter(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); source.onNext(1); source.onNext(2); @@ -233,7 +233,7 @@ public void errorBuffered() { @Test public void errorLatest() { - Observable.fromEmitter(source, Emitter.BackpressureMode.LATEST).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.LATEST).subscribe(ts); source.onNext(1); source.onNext(2); @@ -248,7 +248,7 @@ public void errorLatest() { @Test public void errorNone() { - Observable.fromEmitter(source, Emitter.BackpressureMode.NONE).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.NONE).subscribe(ts); source.onNext(1); source.onNext(2); @@ -263,7 +263,7 @@ public void errorNone() { @Test public void unsubscribedBuffer() { - Observable.fromEmitter(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); ts.unsubscribe(); source.onNext(1); @@ -279,7 +279,7 @@ public void unsubscribedBuffer() { @Test public void unsubscribedLatest() { - Observable.fromEmitter(source, Emitter.BackpressureMode.LATEST).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.LATEST).subscribe(ts); ts.unsubscribe(); source.onNext(1); @@ -295,7 +295,7 @@ public void unsubscribedLatest() { @Test public void unsubscribedError() { - Observable.fromEmitter(source, Emitter.BackpressureMode.ERROR).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.ERROR).subscribe(ts); ts.unsubscribe(); source.onNext(1); @@ -311,7 +311,7 @@ public void unsubscribedError() { @Test public void unsubscribedDrop() { - Observable.fromEmitter(source, Emitter.BackpressureMode.DROP).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.DROP).subscribe(ts); ts.unsubscribe(); source.onNext(1); @@ -327,7 +327,7 @@ public void unsubscribedDrop() { @Test public void unsubscribedNone() { - Observable.fromEmitter(source, Emitter.BackpressureMode.NONE).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.NONE).subscribe(ts); ts.unsubscribe(); source.onNext(1); @@ -343,7 +343,7 @@ public void unsubscribedNone() { @Test public void unsubscribedNoCancelBuffer() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); ts.unsubscribe(); sourceNoCancel.onNext(1); @@ -359,7 +359,7 @@ public void unsubscribedNoCancelBuffer() { @Test public void unsubscribedNoCancelLatest() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); ts.unsubscribe(); sourceNoCancel.onNext(1); @@ -375,7 +375,7 @@ public void unsubscribedNoCancelLatest() { @Test public void unsubscribedNoCancelError() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.ERROR).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.ERROR).subscribe(ts); ts.unsubscribe(); sourceNoCancel.onNext(1); @@ -391,7 +391,7 @@ public void unsubscribedNoCancelError() { @Test public void unsubscribedNoCancelDrop() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.DROP).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.DROP).subscribe(ts); ts.unsubscribe(); sourceNoCancel.onNext(1); @@ -407,7 +407,7 @@ public void unsubscribedNoCancelDrop() { @Test public void unsubscribedNoCancelNone() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.NONE).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.NONE).subscribe(ts); ts.unsubscribe(); sourceNoCancel.onNext(1); @@ -423,7 +423,7 @@ public void unsubscribedNoCancelNone() { @Test public void deferredRequest() { - Observable.fromEmitter(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); source.onNext(1); source.onNext(2); @@ -438,7 +438,7 @@ public void deferredRequest() { @Test public void take() { - Observable.fromEmitter(source, Emitter.BackpressureMode.BUFFER).take(2).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.BUFFER).take(2).subscribe(ts); source.onNext(1); source.onNext(2); @@ -453,7 +453,7 @@ public void take() { @Test public void takeOne() { - Observable.fromEmitter(source, Emitter.BackpressureMode.BUFFER).take(1).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.BUFFER).take(1).subscribe(ts); ts.requestMore(2); source.onNext(1); @@ -467,7 +467,7 @@ public void takeOne() { @Test public void requestExact() { - Observable.fromEmitter(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); + Observable.create(source, Emitter.BackpressureMode.BUFFER).subscribe(ts); ts.requestMore(2); source.onNext(1); @@ -481,7 +481,7 @@ public void requestExact() { @Test public void takeNoCancel() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.BUFFER).take(2).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).take(2).subscribe(ts); sourceNoCancel.onNext(1); sourceNoCancel.onNext(2); @@ -496,7 +496,7 @@ public void takeNoCancel() { @Test public void takeOneNoCancel() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.BUFFER).take(1).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).take(1).subscribe(ts); ts.requestMore(2); sourceNoCancel.onNext(1); @@ -510,7 +510,7 @@ public void takeOneNoCancel() { @Test public void unsubscribeNoCancel() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); ts.requestMore(2); sourceNoCancel.onNext(1); @@ -535,7 +535,7 @@ public void onNext(Integer t) { } }; - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts1); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts1); sourceNoCancel.onNext(1); @@ -546,7 +546,7 @@ public void onNext(Integer t) { @Test public void completeInline() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); sourceNoCancel.onNext(1); sourceNoCancel.onCompleted(); @@ -560,7 +560,7 @@ public void completeInline() { @Test public void errorInline() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts); sourceNoCancel.onNext(1); sourceNoCancel.onError(new TestException()); @@ -582,7 +582,7 @@ public void onNext(Integer t) { } }; - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts1); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.BUFFER).subscribe(ts1); sourceNoCancel.onNext(1); sourceNoCancel.onNext(2); @@ -602,7 +602,7 @@ public void onNext(Integer t) { } }; - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts1); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts1); sourceNoCancel.onNext(1); @@ -621,7 +621,7 @@ public void onNext(Integer t) { } }; - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts1); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts1); sourceNoCancel.onNext(1); @@ -632,7 +632,7 @@ public void onNext(Integer t) { @Test public void completeInlineLatest() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); sourceNoCancel.onNext(1); sourceNoCancel.onCompleted(); @@ -646,7 +646,7 @@ public void completeInlineLatest() { @Test public void completeInlineExactLatest() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); sourceNoCancel.onNext(1); sourceNoCancel.onCompleted(); @@ -660,7 +660,7 @@ public void completeInlineExactLatest() { @Test public void errorInlineLatest() { - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts); sourceNoCancel.onNext(1); sourceNoCancel.onError(new TestException()); @@ -682,7 +682,7 @@ public void onNext(Integer t) { } }; - Observable.fromEmitter(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts1); + Observable.create(sourceNoCancel, Emitter.BackpressureMode.LATEST).subscribe(ts1); sourceNoCancel.onNext(1); sourceNoCancel.onNext(2); diff --git a/src/test/java/rx/internal/operators/OnSubscribeDetachTest.java b/src/test/java/rx/internal/operators/OnSubscribeDetachTest.java index 6d06019425..7d435bff79 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeDetachTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeDetachTest.java @@ -138,7 +138,7 @@ public void deferredUpstreamProducer() { TestSubscriber ts = new TestSubscriber(0); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { subscriber.set(t); diff --git a/src/test/java/rx/internal/operators/OnSubscribeDoOnEachTest.java b/src/test/java/rx/internal/operators/OnSubscribeDoOnEachTest.java index 8cf5bdd2cc..bec5c4daab 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeDoOnEachTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeDoOnEachTest.java @@ -178,7 +178,7 @@ public void testFatalError() { .flatMap(new Func1>() { @Override public Observable call(Integer integer) { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber o) { throw new NullPointerException("Test NPE"); @@ -229,7 +229,7 @@ public void call(Throwable e) { public void testIfOnNextActionFailsEmitsErrorAndDoesNotFollowWithCompleted() { TestSubscriber ts = TestSubscriber.create(); final RuntimeException e1 = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber subscriber) { @@ -259,7 +259,7 @@ public void call(Integer t) { public void testIfOnNextActionFailsEmitsErrorAndDoesNotFollowWithOnNext() { TestSubscriber ts = TestSubscriber.create(); final RuntimeException e1 = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber subscriber) { @@ -298,7 +298,7 @@ public void call(Throwable e) { TestSubscriber ts = TestSubscriber.create(); final RuntimeException e1 = new RuntimeException(); final RuntimeException e2 = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber subscriber) { diff --git a/src/test/java/rx/internal/operators/OnSubscribeFlatMapCompletableTest.java b/src/test/java/rx/internal/operators/OnSubscribeFlatMapCompletableTest.java new file mode 100644 index 0000000000..2b88387400 --- /dev/null +++ b/src/test/java/rx/internal/operators/OnSubscribeFlatMapCompletableTest.java @@ -0,0 +1,557 @@ +/** + * Copyright 2017 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import static org.junit.Assert.*; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.*; + +import rx.*; +import rx.CompletableTest.*; +import rx.Observable; +import rx.exceptions.*; +import rx.functions.*; +import rx.internal.util.UtilityFunctions; +import rx.observers.*; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; +import rx.subscriptions.Subscriptions; + +public class OnSubscribeFlatMapCompletableTest implements Action0, Action1 { + + final AtomicInteger calls = new AtomicInteger(); + + /** A normal Completable object. */ + final NormalCompletable normal = new NormalCompletable(); + + /** An error Completable object. */ + final ErrorCompletable error = new ErrorCompletable(); + + final Func1 identity = UtilityFunctions.identity(); + + @Override + public void call() { + calls.getAndIncrement(); + } + + @Override + public void call(Object t) { + calls.getAndIncrement(); + } + + void assertCalls(int n) { + assertEquals(n, calls.get()); + } + + @Test + public void normal() { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete().doOnCompleted(OnSubscribeFlatMapCompletableTest.this); + } + }) + .test() + .assertResult(); + + assertCalls(10); + } + + @Test + public void normalMaxConcurrent() { + for (int i = 1; i < 10; i++) { + calls.set(0); + + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete() + .observeOn(Schedulers.computation()) + .doOnCompleted(OnSubscribeFlatMapCompletableTest.this); + } + }, false, i) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertResult(); + + assertCalls(10); + } + } + + @Test + public void error() { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.error(new TestException()).doOnError(OnSubscribeFlatMapCompletableTest.this); + } + }) + .test() + .assertFailure(TestException.class); + + assertCalls(1); + } + + @Test + public void errorDelayed() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.error(new TestException()).doOnError(OnSubscribeFlatMapCompletableTest.this); + } + }, true) + .test() + .assertFailure(CompositeException.class); + + List onErrorEvents = as.getOnErrorEvents(); + + assertEquals(onErrorEvents.toString(), 1, onErrorEvents.size()); + + onErrorEvents = ((CompositeException)onErrorEvents.get(0)).getExceptions(); + + assertEquals(onErrorEvents.toString(), 10, onErrorEvents.size()); + + for (Throwable ex : onErrorEvents) { + assertTrue(ex.toString(), ex instanceof TestException); + } + + assertCalls(10); + } + + @Test + public void errorDelayedMaxConcurrency() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.error(new TestException()).doOnError(OnSubscribeFlatMapCompletableTest.this); + } + }, true, 1) + .test() + .assertFailure(CompositeException.class); + + List onErrorEvents = as.getOnErrorEvents(); + + assertEquals(onErrorEvents.toString(), 1, onErrorEvents.size()); + + onErrorEvents = ((CompositeException)onErrorEvents.get(0)).getExceptions(); + + assertEquals(onErrorEvents.toString(), 10, onErrorEvents.size()); + + for (Throwable ex : onErrorEvents) { + assertTrue(ex.toString(), ex instanceof TestException); + } + + assertCalls(10); + } + + @Test + public void mapperThrows() { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mapperNull() { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return null; + } + }) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void paramValidation() { + try { + Observable.range(1, 10) + .flatMapCompletable(null); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertEquals("mapper is null", ex.getMessage()); + } + + try { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete(); + } + }, false, 0); + fail("Should have thrown"); + } catch (IllegalArgumentException ex) { + assertEquals("maxConcurrency > 0 required but it was 0", ex.getMessage()); + } + + try { + Observable.range(1, 10) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete(); + } + }, true, -99); + fail("Should have thrown"); + } catch (IllegalArgumentException ex) { + assertEquals("maxConcurrency > 0 required but it was -99", ex.getMessage()); + } + } + + @Test + public void mainErrorDelayed() { + Observable.range(1, 10).concatWith(Observable.error(new TestException())) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return Completable.complete().doOnCompleted(OnSubscribeFlatMapCompletableTest.this); + } + }, true) + .test() + .assertFailure(TestException.class); + + assertCalls(10); + } + + @Test + public void innerDoubleOnSubscribe() { + final CompletableSubscriber[] inner = { null }; + + AssertableSubscriber as = Observable.just(1) + .flatMapCompletable(new Func1() { + @Override + public Completable call(Integer t) { + return Completable.create(new Completable.OnSubscribe() { + @Override + public void call(CompletableSubscriber t) { + OnSubscribeFlatMapCompletableTest.this.call(); + Subscription s1 = Subscriptions.empty(); + + t.onSubscribe(s1); + + Subscription s2 = Subscriptions.empty(); + + t.onSubscribe(s2); + + if (s2.isUnsubscribed()) { + OnSubscribeFlatMapCompletableTest.this.call(); + } + + t.onCompleted(); + + inner[0] = t; + } + }); + } + }) + .test() + .assertResult(); + + assertCalls(2); + + inner[0].onError(new TestException()); + + as.assertResult(); + } + + @Test + public void mainErrorUnsubscribes() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(); + + ps0.flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return v == 0 ? ps1.toCompletable() : ps2.toCompletable(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void innerErrorUnsubscribes() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(); + + ps0.flatMapCompletable(new Func1() { + @Override + public Completable call(Integer v) { + return v == 0 ? ps1.toCompletable() : ps2.toCompletable(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps1.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + + @Test(timeout = 5000) + public void mergeObservableEmpty() { + Completable c = Observable.empty().flatMapCompletable(identity).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeObservableError() { + Completable c = Observable.error(new TestException()).flatMapCompletable(identity).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeObservableSingle() { + Completable c = Observable.just(normal.completable).flatMapCompletable(identity).toCompletable(); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeObservableSingleThrows() { + Completable c = Observable.just(error.completable).flatMapCompletable(identity).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeObservableMany() { + Completable c = Observable.just(normal.completable).repeat(3).flatMapCompletable(identity).toCompletable(); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeObservableManyOneThrows() { + Completable c = Observable.just(normal.completable, error.completable).flatMapCompletable(identity).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeObservableMaxConcurrent() { + final List requested = new ArrayList(); + Observable cs = Observable + .just(normal.completable) + .repeat(10) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + requested.add(v); + } + }); + + Completable c = cs.flatMapCompletable(identity, false, 5).toCompletable(); + + c.await(); + + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests + Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); + } + + @Test(timeout = 5000) + public void mergeDelayErrorObservableEmpty() { + Completable c = Observable.empty().flatMapCompletable(identity, true).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeDelayErrorObservableError() { + Completable c = Observable.error(new TestException()).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeDelayErrorObservableSingle() { + Completable c = Observable.just(normal.completable).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeDelayErrorObservableSingleThrows() { + Completable c = Observable.just(error.completable).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeDelayErrorObservableMany() { + Completable c = Observable.just(normal.completable).repeat(3).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 5000, expected = TestException.class) + public void mergeDelayErrorObservableManyOneThrows() { + Completable c = Observable.just(normal.completable, error.completable).flatMapCompletable(identity, true).toCompletable(); + + c.await(); + } + + @Test(timeout = 5000) + public void mergeDelayErrorObservableMaxConcurrent() { + final List requested = new ArrayList(); + Observable cs = Observable + .just(normal.completable) + .repeat(10) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + requested.add(v); + } + }); + + Completable c = cs.flatMapCompletable(identity, true, 5).toCompletable(); + + c.await(); + + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests + Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); + } + + @Test + public void asyncObservables() { + + final int[] calls = { 0 }; + + Observable.range(1, 5).map(new Func1() { + @Override + public Completable call(final Integer v) { + System.out.println("Mapping " + v); + return Completable.fromAction(new Action0() { + @Override + public void call() { + System.out.println("Processing " + (calls[0] + 1)); + calls[0]++; + } + }) + .subscribeOn(Schedulers.io()) + .doOnCompleted(new Action0() { + @Override + public void call() { + System.out.println("Inner complete " + v); + } + }) + .observeOn(Schedulers.computation()); + } + }).flatMapCompletable(identity, false, 1).toCompletable() + .test() + .awaitTerminalEventAndUnsubscribeOnTimeout(5, TimeUnit.SECONDS) + .assertResult(); + + Assert.assertEquals(5, calls[0]); + } + +} diff --git a/src/test/java/rx/internal/operators/OnSubscribeFlatMapSingleTest.java b/src/test/java/rx/internal/operators/OnSubscribeFlatMapSingleTest.java new file mode 100644 index 0000000000..3da2ac389c --- /dev/null +++ b/src/test/java/rx/internal/operators/OnSubscribeFlatMapSingleTest.java @@ -0,0 +1,782 @@ +/** + * Copyright 2017 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.internal.operators; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; +import org.junit.Test; + +import rx.*; +import rx.Observable; +import rx.exceptions.*; +import rx.functions.*; +import rx.observers.*; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; + +public class OnSubscribeFlatMapSingleTest implements Action0, Action1 { + + final AtomicInteger calls = new AtomicInteger(); + + final Action1 errorConsumer = new Action1() { + @Override + public void call(Throwable e) { + OnSubscribeFlatMapSingleTest.this.call(e); + } + }; + + @Override + public void call() { + calls.getAndIncrement(); + } + + @Override + public void call(Object t) { + calls.getAndIncrement(); + } + + void assertCalls(int n) { + assertEquals(n, calls.get()); + } + + @Test + public void normal() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void normalBackpressured() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .test(0L) + .assertNoValues() + .requestMore(1) + .assertValues(1) + .requestMore(2) + .assertValues(1, 2, 3) + .requestMore(3) + .assertValues(1, 2, 3, 4, 5, 6) + .requestMore(4) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void normalMaxConcurrencyBackpressured() { + for (int i = 1; i < 16; i++) { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, false, i) + .test(0L) + .assertNoValues() + .requestMore(1) + .assertValues(1) + .requestMore(2) + .assertValues(1, 2, 3) + .requestMore(3) + .assertValues(1, 2, 3, 4, 5, 6) + .requestMore(4) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + } + + @Test + public void normalMaxConcurrent() { + for (int i = 1; i < 16; i++) { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, false, i) + .test() + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + } + + @Test + public void normalMaxConcurrentAsync() { + for (int i = 1; i < 2; i++) { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }, false, i) + .test(); + + as.awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertValueCount(10) + .assertNoErrors() + .assertCompleted(); + + Set set = new HashSet(as.getOnNextEvents()); + + for (int j = 1; j < 11; j++) { + assertTrue("" + set, set.contains(j)); + } + } + } + + @Test + public void justMaxConcurrentAsync() { + AssertableSubscriber as = Observable.just(1) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }, false, 1) + .test(); + + as.awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertResult(1); + } + + @Test + public void error() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.error(new TestException()).doOnError(errorConsumer); + } + }) + .test() + .assertFailure(TestException.class); + + assertCalls(1); + } + + @Test + public void errorDelayed() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.error(new TestException()).doOnError(errorConsumer); + } + }, true) + .test() + .assertFailure(CompositeException.class); + + List onErrorEvents = as.getOnErrorEvents(); + + assertEquals(onErrorEvents.toString(), 1, onErrorEvents.size()); + + onErrorEvents = ((CompositeException)onErrorEvents.get(0)).getExceptions(); + + assertEquals(onErrorEvents.toString(), 10, onErrorEvents.size()); + + for (Throwable ex : onErrorEvents) { + assertTrue(ex.toString(), ex instanceof TestException); + } + + assertCalls(10); + } + + @Test + public void errorDelayedMaxConcurrency() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.error(new TestException()).doOnError(errorConsumer); + } + }, true, 1) + .test() + .assertFailure(CompositeException.class); + + List onErrorEvents = as.getOnErrorEvents(); + + assertEquals(onErrorEvents.toString(), 1, onErrorEvents.size()); + + onErrorEvents = ((CompositeException)onErrorEvents.get(0)).getExceptions(); + + assertEquals(onErrorEvents.toString(), 10, onErrorEvents.size()); + + for (Throwable ex : onErrorEvents) { + assertTrue(ex.toString(), ex instanceof TestException); + } + + assertCalls(10); + } + + @Test + public void mapperThrows() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mapperNull() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return null; + } + }) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void paramValidation() { + try { + Observable.range(1, 10) + .flatMapSingle(null); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertEquals("mapper is null", ex.getMessage()); + } + + try { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, false, 0); + fail("Should have thrown"); + } catch (IllegalArgumentException ex) { + assertEquals("maxConcurrency > 0 required but it was 0", ex.getMessage()); + } + + try { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, true, -99); + fail("Should have thrown"); + } catch (IllegalArgumentException ex) { + assertEquals("maxConcurrency > 0 required but it was -99", ex.getMessage()); + } + } + + @Test + public void mainErrorDelayed() { + Observable.range(1, 10).concatWith(Observable.error(new TestException())) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).doOnSuccess(OnSubscribeFlatMapSingleTest.this); + } + }, true) + .test() + .assertFailure(TestException.class, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + assertCalls(10); + } + + @Test + public void mainErrorUnsubscribes() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void innerErrorUnsubscribes() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps1.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void take() { + Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .take(5) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + + @Test + public void unsubscribe() { + AssertableSubscriber as = Observable.range(1, 10) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .test(0) + ; + + as.unsubscribe(); + + as.assertNoValues().assertNoErrors().assertNotCompleted(); + } + + @Test + public void mainErrorUnsubscribesNoRequest() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(0L); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void innerErrorUnsubscribesNoRequest() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(0L); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps1.onError(new TestException()); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + @Test + public void mainErrorUnsubscribesNoRequestDelayError() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(0L); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }, true).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onError(new TestException()); + ps1.onNext(3); + ps1.onCompleted(); + ps2.onNext(4); + ps2.onCompleted(); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + + as.requestMore(2); + as.assertValues(3, 4); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void innerErrorUnsubscribesNoRequestDelayError() { + PublishSubject ps0 = PublishSubject.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + + TestSubscriber as = TestSubscriber.create(0L); + + ps0.flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return v == 0 ? ps1.toSingle() : ps2.toSingle(); + } + }, true).unsafeSubscribe(as); + + assertTrue(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(0); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onNext(1); + + assertTrue(ps0.hasObservers()); + assertTrue(ps1.hasObservers()); + assertTrue(ps2.hasObservers()); + as.assertNoValues(); + as.assertNoErrors(); + as.assertNotCompleted(); + + ps0.onCompleted(); + ps1.onError(new TestException()); + ps2.onNext(4); + ps2.onCompleted(); + + assertFalse(ps0.hasObservers()); + assertFalse(ps1.hasObservers()); + assertFalse(ps2.hasObservers()); + + as.requestMore(1); + as.assertValues(4); + as.assertError(TestException.class); + as.assertNotCompleted(); + } + + @Test + public void justBackpressured() { + Observable.just(1) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }) + .test(1L) + .assertResult(1); + } + + @Test + public void justBackpressuredDelayError() { + Observable.just(1) + .flatMapSingle(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + }, true) + .test(1L) + .assertResult(1); + } + + @Test + public void singleMerge() { + Single.merge(Observable.range(1, 10).map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + })) + .test() + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void singleMergeMaxConcurrent() { + AssertableSubscriber as = Single.merge(Observable.range(1, 10).map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }), 2) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS); + + Set set = new HashSet(as.getOnNextEvents()); + + assertEquals("" + set, 10, set.size()); + for (int j = 1; j < 11; j++) { + assertTrue("" + set, set.contains(j)); + } + } + + @Test + public void singleMergeDelayError() { + Single.mergeDelayError(Observable.range(1, 10).map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + })) + .test() + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void singleMergeDelayErrorMaxConcurrent() { + AssertableSubscriber as = Single.mergeDelayError(Observable.range(1, 10).map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }), 2) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS); + + Set set = new HashSet(as.getOnNextEvents()); + + assertEquals("" + set, 10, set.size()); + for (int j = 1; j < 11; j++) { + assertTrue("" + set, set.contains(j)); + } + } + + @Test + public void singleMergeDelayErrorWithError() { + Single.mergeDelayError(Observable.range(1, 10) + .concatWith(Observable.error(new TestException())) + .map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v); + } + })) + .test() + .assertFailure(TestException.class, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void singleMergeDelayMaxConcurrentErrorWithError() { + AssertableSubscriber as = Single.mergeDelayError(Observable.range(1, 10) + .concatWith(Observable.error(new TestException())) + .map(new Func1>() { + @Override + public Single call(Integer v) { + return Single.just(v).observeOn(Schedulers.computation()); + } + }), 2) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS); + + Set set = new HashSet(as.getOnNextEvents()); + + assertEquals("" + set, 10, set.size()); + for (int j = 1; j < 11; j++) { + assertTrue("" + set, set.contains(j)); + } + } +} diff --git a/src/test/java/rx/internal/operators/OnSubscribeFromArrayTest.java b/src/test/java/rx/internal/operators/OnSubscribeFromArrayTest.java index cd117169d2..1d5368f097 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeFromArrayTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeFromArrayTest.java @@ -29,7 +29,7 @@ Observable create(int n) { for (int i = 0; i < n; i++) { array[i] = i; } - return Observable.create(new OnSubscribeFromArray(array)); + return Observable.unsafeCreate(new OnSubscribeFromArray(array)); } @Test public void simple() { diff --git a/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java b/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java index 43993ca137..0464ed8dc0 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeFromIterableTest.java @@ -38,12 +38,12 @@ public class OnSubscribeFromIterableTest { @Test(expected = NullPointerException.class) public void testNull() { - Observable.create(new OnSubscribeFromIterable(null)); + Observable.unsafeCreate(new OnSubscribeFromIterable(null)); } @Test public void testListIterable() { - Observable observable = Observable.create(new OnSubscribeFromIterable(Arrays. asList("one", "two", "three"))); + Observable observable = Observable.unsafeCreate(new OnSubscribeFromIterable(Arrays. asList("one", "two", "three"))); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @@ -86,7 +86,7 @@ public void remove() { } }; - Observable observable = Observable.create(new OnSubscribeFromIterable(it)); + Observable observable = Observable.unsafeCreate(new OnSubscribeFromIterable(it)); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); diff --git a/src/test/java/rx/internal/operators/OnSubscribeMapTest.java b/src/test/java/rx/internal/operators/OnSubscribeMapTest.java index 6477bdbdc5..12c24cb303 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeMapTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeMapTest.java @@ -299,7 +299,7 @@ public void call(Object object) { }; try { - Observable.create(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); + Observable.unsafeCreate(creator).flatMap(manyMapper).map(mapper).subscribe(onNext); } catch (RuntimeException e) { e.printStackTrace(); throw e; diff --git a/src/test/java/rx/internal/operators/OnSubscribeReduceTest.java b/src/test/java/rx/internal/operators/OnSubscribeReduceTest.java index 9ae07b6112..7d0f1a6cc3 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeReduceTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeReduceTest.java @@ -145,7 +145,7 @@ public void testBackpressureWithInitialValue() throws InterruptedException { @Test public void testNoInitialValueDoesNotEmitMultipleTerminalEvents() { TestSubscriber ts = TestSubscriber.create(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -176,7 +176,7 @@ public Integer call(Integer a, Integer b) { @Test public void testNoInitialValueUpstreamEmitsMoreOnNextDespiteUnsubscription() { TestSubscriber ts = TestSubscriber.create(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -226,7 +226,7 @@ public void call(Throwable t) { TestSubscriber ts = TestSubscriber.create(); final RuntimeException e1 = new RuntimeException("e1"); final Throwable e2 = new RuntimeException("e2"); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { diff --git a/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java b/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java index e0761a576a..a824f3191b 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeRefCountTest.java @@ -19,6 +19,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; +import java.lang.management.ManagementFactory; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -31,6 +32,7 @@ import rx.Observable.OnSubscribe; import rx.Observer; import rx.functions.*; +import rx.observables.ConnectableObservable; import rx.observers.*; import rx.schedulers.*; import rx.subjects.ReplaySubject; @@ -322,7 +324,7 @@ public void call() { } private Observable synchronousInterval() { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -341,7 +343,7 @@ public void call(Subscriber subscriber) { public void onlyFirstShouldSubscribeAndLastUnsubscribe() { final AtomicInteger subscriptionCount = new AtomicInteger(); final AtomicInteger unsubscriptionCount = new AtomicInteger(); - Observable observable = Observable.create(new OnSubscribe() { + Observable observable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { subscriptionCount.incrementAndGet(); @@ -611,4 +613,155 @@ public void call(Throwable t) { assertNotNull("First subscriber didn't get the error", err1); assertNotNull("Second subscriber didn't get the error", err2); } + + Observable source; + + @Test + public void replayNoLeak() throws Exception { + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }) + .replay(1) + .refCount(); + + source.subscribe(); + + System.gc(); + Thread.sleep(100); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + @Test + public void replayNoLeak2() throws Exception { + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Observable.never()) + .replay(1) + .refCount(); + + Subscription s1 = source.subscribe(); + Subscription s2 = source.subscribe(); + + s1.unsubscribe(); + s2.unsubscribe(); + + s1 = null; + s2 = null; + + System.gc(); + Thread.sleep(100); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + static final class ExceptionData extends Exception { + private static final long serialVersionUID = -6763898015338136119L; + + public final Object data; + + public ExceptionData(Object data) { + this.data = data; + } + } + + @Test + public void publishNoLeak() throws Exception { + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + throw new ExceptionData(new byte[100 * 1000 * 1000]); + } + }) + .publish() + .refCount(); + + Action1 err = Actions.empty(); + source.subscribe(Actions.empty(), err); + + System.gc(); + Thread.sleep(100); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + @Test + public void publishNoLeak2() throws Exception { + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Observable.never()) + .publish() + .refCount(); + + Subscription s1 = source.test(0); + Subscription s2 = source.test(0); + + s1.unsubscribe(); + s2.unsubscribe(); + + s1 = null; + s2 = null; + + System.gc(); + Thread.sleep(100); + + long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = null; + assertTrue(String.format("%,3d -> %,3d%n", start, after), start + 20 * 1000 * 1000 > after); + } + + @Test + public void replayIsUnsubscribed() { + ConnectableObservable co = Observable.just(1) + .replay(); + + assertTrue(((Subscription)co).isUnsubscribed()); + + Subscription s = co.connect(); + + assertFalse(((Subscription)co).isUnsubscribed()); + + s.unsubscribe(); + + assertTrue(((Subscription)co).isUnsubscribed()); + } } diff --git a/src/test/java/rx/internal/operators/OnSubscribeToMapTest.java b/src/test/java/rx/internal/operators/OnSubscribeToMapTest.java index 1ed6a542a8..97b7f33673 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeToMapTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeToMapTest.java @@ -301,7 +301,7 @@ public Map call() { public void testFactoryFailureDoesNotAllowErrorAndCompletedEmissions() { TestSubscriber> ts = TestSubscriber.create(0); final RuntimeException e = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -342,7 +342,7 @@ public void call(Throwable t) { TestSubscriber> ts = TestSubscriber.create(0); final RuntimeException e1 = new RuntimeException(); final RuntimeException e2 = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -377,7 +377,7 @@ public Integer call(Integer t) { public void testFactoryFailureDoesNotAllowErrorThenOnNextEmissions() { TestSubscriber> ts = TestSubscriber.create(0); final RuntimeException e = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { diff --git a/src/test/java/rx/internal/operators/OnSubscribeToMultimapTest.java b/src/test/java/rx/internal/operators/OnSubscribeToMultimapTest.java index 91fb03c0be..a27c2ae662 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeToMultimapTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeToMultimapTest.java @@ -395,7 +395,7 @@ public Collection call(Integer k) { public void testKeySelectorFailureDoesNotAllowErrorAndCompletedEmissions() { TestSubscriber>> ts = TestSubscriber.create(0); final RuntimeException e = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -436,7 +436,7 @@ public void call(Throwable t) { TestSubscriber>> ts = TestSubscriber.create(0); final RuntimeException e1 = new RuntimeException(); final RuntimeException e2 = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -471,7 +471,7 @@ public Integer call(Integer t) { public void testFactoryFailureDoesNotAllowErrorThenOnNextEmissions() { TestSubscriber>> ts = TestSubscriber.create(0); final RuntimeException e = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { diff --git a/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java b/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java index e169cbb68e..3a93d4cc8f 100644 --- a/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java +++ b/src/test/java/rx/internal/operators/OnSubscribeUsingTest.java @@ -254,7 +254,7 @@ public Subscription call() { Func1> observableFactory = new Func1>() { @Override public Observable call(Subscription subscription) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { throw new TestException(); diff --git a/src/test/java/rx/internal/operators/OperatorAllTest.java b/src/test/java/rx/internal/operators/OperatorAllTest.java index 0ca615d515..5ac74526fa 100644 --- a/src/test/java/rx/internal/operators/OperatorAllTest.java +++ b/src/test/java/rx/internal/operators/OperatorAllTest.java @@ -186,7 +186,7 @@ public Boolean call(Object object) { @Test public void testDoesNotEmitMultipleTerminalEvents() { TestSubscriber ts = TestSubscriber.create(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -216,7 +216,7 @@ public Boolean call(Integer t) { @Test public void testUpstreamEmitsOnNextAfterFailureWithoutCheckingSubscription() { TestSubscriber ts = TestSubscriber.create(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -263,7 +263,7 @@ public void call(Throwable t) { TestSubscriber ts = TestSubscriber.create(); final RuntimeException e1 = new RuntimeException(); final Throwable e2 = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { diff --git a/src/test/java/rx/internal/operators/OperatorAnyTest.java b/src/test/java/rx/internal/operators/OperatorAnyTest.java index 776d6e8f72..0eaf6b07ec 100644 --- a/src/test/java/rx/internal/operators/OperatorAnyTest.java +++ b/src/test/java/rx/internal/operators/OperatorAnyTest.java @@ -278,7 +278,7 @@ public Boolean call(Object object) { @Test public void testUpstreamEmitsOnNextAfterFailureWithoutCheckingSubscription() { TestSubscriber ts = TestSubscriber.create(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -314,7 +314,7 @@ public Boolean call(Integer t) { @Test public void testUpstreamEmitsOnNextWithoutCheckingSubscription() { TestSubscriber ts = TestSubscriber.create(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -356,7 +356,7 @@ public void call(Throwable t) { TestSubscriber ts = TestSubscriber.create(); final RuntimeException e1 = new RuntimeException(); final Throwable e2 = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { diff --git a/src/test/java/rx/internal/operators/OperatorBufferTest.java b/src/test/java/rx/internal/operators/OperatorBufferTest.java index 99f4cf5c0f..5cd3b2fa96 100644 --- a/src/test/java/rx/internal/operators/OperatorBufferTest.java +++ b/src/test/java/rx/internal/operators/OperatorBufferTest.java @@ -52,7 +52,7 @@ public void before() { @Test public void testComplete() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onCompleted(); @@ -69,7 +69,7 @@ public void call(Subscriber observer) { @Test public void testSkipAndCountOverlappingBuffers() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -94,7 +94,7 @@ public void call(Subscriber observer) { @Test public void testSkipAndCountGaplessBuffers() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -119,7 +119,7 @@ public void call(Subscriber observer) { @Test public void testSkipAndCountBuffersWithGaps() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -144,7 +144,7 @@ public void call(Subscriber observer) { @Test public void testTimedAndCount() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -175,7 +175,7 @@ public void call(Subscriber observer) { @Test public void testTimed() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 97); @@ -208,7 +208,7 @@ public void call(Subscriber observer) { @Test public void testObservableBasedOpenerAndCloser() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -220,7 +220,7 @@ public void call(Subscriber observer) { } }); - Observable openings = Observable.create(new Observable.OnSubscribe() { + Observable openings = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 50); @@ -232,7 +232,7 @@ public void call(Subscriber observer) { Func1> closer = new Func1>() { @Override public Observable call(Object opening) { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 100); @@ -256,7 +256,7 @@ public void call(Subscriber observer) { @Test public void testObservableBasedCloser() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -271,7 +271,7 @@ public void call(Subscriber observer) { Func0> closer = new Func0>() { @Override public Observable call() { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 100); @@ -786,7 +786,7 @@ public void testProducerRequestThroughBufferWithSize1() { TestSubscriber> ts = new TestSubscriber>(); ts.requestMore(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -811,7 +811,7 @@ public void request(long n) { public void testProducerRequestThroughBufferWithSize2() { TestSubscriber> ts = new TestSubscriber>(); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -834,7 +834,7 @@ public void testProducerRequestThroughBufferWithSize3() { TestSubscriber> ts = new TestSubscriber>(); ts.requestMore(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -858,7 +858,7 @@ public void request(long n) { public void testProducerRequestThroughBufferWithSize4() { TestSubscriber> ts = new TestSubscriber>(); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -882,7 +882,7 @@ public void testProducerRequestOverflowThroughBufferWithSize1() { TestSubscriber> ts = new TestSubscriber>(); ts.requestMore(Long.MAX_VALUE / 2); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -905,7 +905,7 @@ public void testProducerRequestOverflowThroughBufferWithSize2() { TestSubscriber> ts = new TestSubscriber>(); ts.requestMore(Long.MAX_VALUE / 2); final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber s) { @@ -926,7 +926,7 @@ public void request(long n) { @Test public void testProducerRequestOverflowThroughBufferWithSize3() { final AtomicLong requested = new AtomicLong(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber s) { diff --git a/src/test/java/rx/internal/operators/OperatorConcatTest.java b/src/test/java/rx/internal/operators/OperatorConcatTest.java index 564eb9e709..1e818accda 100644 --- a/src/test/java/rx/internal/operators/OperatorConcatTest.java +++ b/src/test/java/rx/internal/operators/OperatorConcatTest.java @@ -115,7 +115,7 @@ public void testConcatObservableOfObservables() { final Observable odds = Observable.from(o); final Observable even = Observable.from(e); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { @@ -144,7 +144,7 @@ public void testSimpleAsyncConcat() { TestObservable o1 = new TestObservable("one", "two", "three"); TestObservable o2 = new TestObservable("four", "five", "six"); - Observable.concat(Observable.create(o1), Observable.create(o2)).subscribe(observer); + Observable.concat(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)).subscribe(observer); try { // wait for async observables to complete @@ -179,7 +179,7 @@ public void testNestedAsyncConcat() throws Throwable { final AtomicReference parent = new AtomicReference(); final CountDownLatch parentHasStarted = new CountDownLatch(1); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(final Subscriber> observer) { @@ -193,12 +193,12 @@ public void run() { // emit first if (!s.isUnsubscribed()) { System.out.println("Emit o1"); - observer.onNext(Observable.create(o1)); + observer.onNext(Observable.unsafeCreate(o1)); } // emit second if (!s.isUnsubscribed()) { System.out.println("Emit o2"); - observer.onNext(Observable.create(o2)); + observer.onNext(Observable.unsafeCreate(o2)); } // wait until sometime later and emit third @@ -209,7 +209,7 @@ public void run() { } if (!s.isUnsubscribed()) { System.out.println("Emit o3"); - observer.onNext(Observable.create(o3)); + observer.onNext(Observable.unsafeCreate(o3)); } } catch (Throwable e) { @@ -285,7 +285,7 @@ public void testBlockedObservableOfObservables() { final CountDownLatch callOnce = new CountDownLatch(1); final CountDownLatch okToContinue = new CountDownLatch(1); TestObservable> observableOfObservables = new TestObservable>(callOnce, okToContinue, odds, even); - Observable concatF = Observable.concat(Observable.create(observableOfObservables)); + Observable concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); concatF.subscribe(observer); try { //Block main thread to allow observables to serve up o1. @@ -323,8 +323,8 @@ public void testConcatConcurrentWithInfinity() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @SuppressWarnings("unchecked") - TestObservable> observableOfObservables = new TestObservable>(Observable.create(w1), Observable.create(w2)); - Observable concatF = Observable.concat(Observable.create(observableOfObservables)); + TestObservable> observableOfObservables = new TestObservable>(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); + Observable concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); concatF.take(50).subscribe(observer); @@ -357,13 +357,13 @@ public void testConcatNonBlockingObservables() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { // simulate what would happen in an observable - observer.onNext(Observable.create(w1)); - observer.onNext(Observable.create(w2)); + observer.onNext(Observable.unsafeCreate(w1)); + observer.onNext(Observable.unsafeCreate(w2)); observer.onCompleted(); } @@ -408,7 +408,7 @@ public void testConcatUnsubscribe() { @SuppressWarnings("unchecked") final Observer observer = mock(Observer.class); - final Observable concat = Observable.concat(Observable.create(w1), Observable.create(w2)); + final Observable concat = Observable.concat(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); try { // Subscribe @@ -450,8 +450,8 @@ public void testConcatUnsubscribeConcurrent() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @SuppressWarnings("unchecked") - TestObservable> observableOfObservables = new TestObservable>(Observable.create(w1), Observable.create(w2)); - Observable concatF = Observable.concat(Observable.create(observableOfObservables)); + TestObservable> observableOfObservables = new TestObservable>(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); + Observable concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); Subscription s1 = concatF.subscribe(observer); @@ -618,7 +618,7 @@ public void testMultipleObservers() { @Test public void concatVeryLongObservableOfObservables() { final int n = 10000; - Observable> source = Observable.create(new OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new OnSubscribe>() { @Override public void call(Subscriber> s) { for (int i = 0; i < n; i++) { @@ -650,7 +650,7 @@ public void call(Subscriber> s) { @Test public void concatVeryLongObservableOfObservablesTakeHalf() { final int n = 10000; - Observable> source = Observable.create(new OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new OnSubscribe>() { @Override public void call(Subscriber> s) { for (int i = 0; i < n; i++) { @@ -724,7 +724,7 @@ public void testInnerBackpressureWithoutAlignedBoundaries() { // https://github.com/ReactiveX/RxJava/issues/1818 @Test public void testConcatWithNonCompliantSourceDoubleOnComplete() { - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { diff --git a/src/test/java/rx/internal/operators/OperatorDebounceTest.java b/src/test/java/rx/internal/operators/OperatorDebounceTest.java index ae509c743d..bf3cad52d5 100644 --- a/src/test/java/rx/internal/operators/OperatorDebounceTest.java +++ b/src/test/java/rx/internal/operators/OperatorDebounceTest.java @@ -57,7 +57,7 @@ public void before() { @Test public void testDebounceWithCompleted() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 100, "one"); // Should be skipped since "two" will arrive before the timeout expires. @@ -82,7 +82,7 @@ public void call(Subscriber observer) { @Test public void testDebounceNeverEmits() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { // all should be skipped since they are happening faster than the 200ms timeout @@ -111,7 +111,7 @@ public void call(Subscriber observer) { @Test public void testDebounceWithError() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { Exception error = new TestException(); diff --git a/src/test/java/rx/internal/operators/OperatorDoOnRequestTest.java b/src/test/java/rx/internal/operators/OperatorDoOnRequestTest.java index e471376e93..d1ddd22c85 100644 --- a/src/test/java/rx/internal/operators/OperatorDoOnRequestTest.java +++ b/src/test/java/rx/internal/operators/OperatorDoOnRequestTest.java @@ -99,7 +99,7 @@ public void dontRequestIfDownstreamRequestsLate() { final AtomicReference producer = new AtomicReference(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.setProducer(new Producer() { diff --git a/src/test/java/rx/internal/operators/OperatorDoOnSubscribeTest.java b/src/test/java/rx/internal/operators/OperatorDoOnSubscribeTest.java index 4765a5dfe3..2f60a9e8c5 100644 --- a/src/test/java/rx/internal/operators/OperatorDoOnSubscribeTest.java +++ b/src/test/java/rx/internal/operators/OperatorDoOnSubscribeTest.java @@ -76,7 +76,7 @@ public void testDoOnUnSubscribeWorksWithRefCount() throws Exception { final AtomicInteger countBefore = new AtomicInteger(); final AtomicInteger countAfter = new AtomicInteger(); final AtomicReference> sref = new AtomicReference>(); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { diff --git a/src/test/java/rx/internal/operators/OperatorFlatMapTest.java b/src/test/java/rx/internal/operators/OperatorFlatMapTest.java index 61805d7372..286a2a03bc 100644 --- a/src/test/java/rx/internal/operators/OperatorFlatMapTest.java +++ b/src/test/java/rx/internal/operators/OperatorFlatMapTest.java @@ -468,7 +468,7 @@ public Observable call(Integer t) { } } } - @Test(timeout = 30000) + @Test(timeout = 60000) public void flatMapRangeMixedAsyncLoop() { for (int i = 0; i < 2000; i++) { if (i % 10 == 0) { diff --git a/src/test/java/rx/internal/operators/OperatorGroupByTest.java b/src/test/java/rx/internal/operators/OperatorGroupByTest.java index 2d90e6f29a..b9c2bc6ece 100644 --- a/src/test/java/rx/internal/operators/OperatorGroupByTest.java +++ b/src/test/java/rx/internal/operators/OperatorGroupByTest.java @@ -39,8 +39,10 @@ import rx.functions.*; import rx.internal.util.*; import rx.observables.GroupedObservable; +import rx.observers.AssertableSubscriber; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; public class OperatorGroupByTest { @@ -189,7 +191,7 @@ public void testGroupedEventStream() throws Throwable { final int count = 100; final int groupCount = 2; - Observable es = Observable.create(new Observable.OnSubscribe() { + Observable es = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -602,7 +604,7 @@ public void call(String s) { public void testFirstGroupsCompleteAndParentSlowToThenEmitFinalGroupsAndThenComplete() throws InterruptedException { final CountDownLatch first = new CountDownLatch(2); // there are two groups to first complete final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -680,7 +682,7 @@ public void testFirstGroupsCompleteAndParentSlowToThenEmitFinalGroupsWhichThenSu System.err.println("----------------------------------------------------------------------------------------------"); final CountDownLatch first = new CountDownLatch(2); // there are two groups to first complete final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -771,7 +773,7 @@ public void call(String s) { public void testFirstGroupsCompleteAndParentSlowToThenEmitFinalGroupsWhichThenObservesOnAndDelaysAndThenCompletes() throws InterruptedException { final CountDownLatch first = new CountDownLatch(2); // there are two groups to first complete final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -847,7 +849,7 @@ public void call(String s) { @Test public void testGroupsWithNestedSubscribeOn() throws InterruptedException { final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -903,7 +905,7 @@ public void call(String s) { @Test public void testGroupsWithNestedObserveOn() throws InterruptedException { final ArrayList results = new ArrayList(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -963,7 +965,7 @@ Observable ASYNC_INFINITE_OBSERVABLE_OF_EVENT(final int numGroups, final } Observable SYNC_INFINITE_OBSERVABLE_OF_EVENT(final int numGroups, final AtomicInteger subscribeCounter, final AtomicInteger sentEventCounter) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber op) { @@ -1375,7 +1377,7 @@ public void call(String s) { @Test public void testGroupByUnsubscribe() { final Subscription s = mock(Subscription.class); - Observable o = Observable.create( + Observable o = Observable.unsafeCreate( new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -1419,7 +1421,7 @@ public void onNext(GroupedObservable o) { } } }); - Observable.create( + Observable.unsafeCreate( new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -2017,4 +2019,260 @@ public Map call(Action1 t) { throw exception; }}; } + + @Test + public void outerConsumedInABoundedManner() { + final int[] counter = { 0 }; + + Observable.range(1, 10000) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + counter[0] += v; + } + }) + .groupBy(new Func1() { + @Override + public Integer call(Integer v) { + return 1; + } + }) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(GroupedObservable v) { + return v; + } + }) + .test(0); + + int c = counter[0]; + assertTrue("" + c, c > 0); + assertTrue("" + c, c < 10000); + } + + @Test + public void groupByEvictingMapFactoryThrows() { + final RuntimeException ex = new RuntimeException("boo"); + Func1, Map> evictingMapFactory = // + new Func1, Map>() { + + @Override + public Map call(final Action1 notify) { + throw ex; + } + }; + Observable.just(1) + .groupBy(UtilityFunctions.identity(), UtilityFunctions.identity(), 16, true, evictingMapFactory) + .test() + .assertNoValues() + .assertError(ex); + } + + @Test + public void groupByEvictingMapFactoryExpiryCompletesGroupedFlowable() { + final List completed = new CopyOnWriteArrayList(); + Func1, Map> evictingMapFactory = createEvictingMapFactorySynchronousOnly(1); + PublishSubject subject = PublishSubject.create(); + AssertableSubscriber ts = subject + .groupBy(UtilityFunctions.identity(), UtilityFunctions.identity(), 16, true, evictingMapFactory) + .flatMap(addCompletedKey(completed)) + .test(); + subject.onNext(1); + subject.onNext(2); + subject.onNext(3); + ts.assertValues(1, 2, 3) + .assertNoTerminalEvent(); + assertEquals(Arrays.asList(1, 2), completed); + //ensure coverage of the code that clears the evicted queue + subject.onCompleted(); + ts.assertCompleted(); + ts.assertValueCount(3); + } + + private static final Func1 mod5 = new Func1() { + + @Override + public Integer call(Integer n) { + return n % 5; + } + }; + + @Test + public void groupByEvictingMapFactoryWithExpiringGuavaCacheDemonstrationCodeForUseInJavadoc() { + //javadoc will be a version of this using lambdas and without assertions + final List completed = new CopyOnWriteArrayList(); + //size should be less than 5 to notice the effect + Func1, Map> evictingMapFactory = createEvictingMapFactoryGuava(3); + int numValues = 1000; + Observable.range(1, numValues) + .groupBy(mod5, UtilityFunctions.identity(), 16, true, evictingMapFactory) + .flatMap(addCompletedKey(completed)) + .test() + .assertCompleted() + .assertValueCount(numValues); + //the exact eviction behaviour of the guava cache is not specified so we make some approximate tests + assertTrue(completed.size() > numValues * 0.9); + } + + @Test + public void groupByEvictingMapFactoryEvictionQueueClearedOnErrorCoverageOnly() { + Func1, Map> evictingMapFactory = createEvictingMapFactorySynchronousOnly(1); + PublishSubject subject = PublishSubject.create(); + AssertableSubscriber ts = subject + .groupBy(UtilityFunctions.identity(), UtilityFunctions.identity(), 16, true, evictingMapFactory) + .flatMap(new Func1, Observable>() { + @Override + public Observable call(GroupedObservable g) { + return g; + } + }) + .test(); + RuntimeException ex = new RuntimeException(); + //ensure coverage of the code that clears the evicted queue + subject.onError(ex); + ts.assertNoValues() + .assertError(ex); + } + + private static Func1, Observable> addCompletedKey( + final List completed) { + return new Func1, Observable>() { + @Override + public Observable call(final GroupedObservable g) { + return g.doOnCompleted(new Action0() { + @Override + public void call() { + completed.add(g.getKey()); + } + }); + } + }; + } + + //not thread safe + private static final class SingleThreadEvictingHashMap implements Map { + + private final List list = new ArrayList(); + private final Map map = new HashMap(); + private final int maxSize; + private final Action1 evictedListener; + + SingleThreadEvictingHashMap(int maxSize, Action1 evictedListener) { + this.maxSize = maxSize; + this.evictedListener = evictedListener; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public V get(Object key) { + return map.get(key); + } + + @Override + public V put(K key, V value) { + list.remove(key); + V v; + if (maxSize > 0 && list.size() == maxSize) { + //remove first + K k = list.get(0); + list.remove(0); + v = map.remove(k); + } else { + v = null; + } + list.add(key); + V result = map.put(key, value); + if (v != null) { + evictedListener.call(v); + } + return result; + } + + @Override + public V remove(Object key) { + list.remove(key); + return map.remove(key); + } + + @Override + public void putAll(Map m) { + for (Entry entry: m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + list.clear(); + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + } + + private static Func1, Map> createEvictingMapFactoryGuava(final int maxSize) { + Func1, Map> evictingMapFactory = // + new Func1, Map>() { + + @Override + public Map call(final Action1 notify) { + return CacheBuilder.newBuilder() // + .maximumSize(maxSize) // + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + notify.call(notification.getValue()); + }}) + . build() + .asMap(); + }}; + return evictingMapFactory; + } + + private static Func1, Map> createEvictingMapFactorySynchronousOnly(final int maxSize) { + Func1, Map> evictingMapFactory = // + new Func1, Map>() { + + @Override + public Map call(final Action1 notify) { + return new SingleThreadEvictingHashMap(maxSize, new Action1() { + @Override + public void call(Object object) { + notify.call(object); + }}); + }}; + return evictingMapFactory; + } } diff --git a/src/test/java/rx/internal/operators/OperatorMaterializeTest.java b/src/test/java/rx/internal/operators/OperatorMaterializeTest.java index af090b4379..a55758d25c 100644 --- a/src/test/java/rx/internal/operators/OperatorMaterializeTest.java +++ b/src/test/java/rx/internal/operators/OperatorMaterializeTest.java @@ -29,9 +29,12 @@ import rx.Notification; import rx.Observable; import rx.Subscriber; +import rx.TestUtil; +import rx.functions.Action0; import rx.functions.Action1; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; public class OperatorMaterializeTest { @@ -43,7 +46,7 @@ public void testMaterialize1() { "three"); TestObserver Observer = new TestObserver(); - Observable> m = Observable.create(o1).materialize(); + Observable> m = Observable.unsafeCreate(o1).materialize(); m.subscribe(Observer); try { @@ -69,7 +72,7 @@ public void testMaterialize2() { final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", "three"); TestObserver Observer = new TestObserver(); - Observable> m = Observable.create(o1).materialize(); + Observable> m = Observable.unsafeCreate(o1).materialize(); m.subscribe(Observer); try { @@ -94,7 +97,7 @@ public void testMaterialize2() { public void testMultipleSubscribes() throws InterruptedException, ExecutionException { final TestAsyncErrorObservable o = new TestAsyncErrorObservable("one", "two", null, "three"); - Observable> m = Observable.create(o).materialize(); + Observable> m = Observable.unsafeCreate(o).materialize(); assertEquals(3, m.toList().toBlocking().toFuture().get().size()); assertEquals(3, m.toList().toBlocking().toFuture().get().size()); @@ -201,6 +204,33 @@ public void testUnsubscribeJustBeforeCompletionNotificationShouldPreventThatNoti ts.assertUnsubscribed(); } + @Test + public void testConcurrency() { + for (int i = 0; i < 1000; i++) { + final TestSubscriber> ts = TestSubscriber.create(0); + final PublishSubject ps = PublishSubject.create(); + Action0 publishAction = new Action0() { + @Override + public void call() { + ps.onCompleted(); + } + }; + + Action0 requestAction = new Action0() { + @Override + public void call() { + ts.requestMore(1); + } + }; + + ps.materialize().subscribe(ts); + TestUtil.race(publishAction, requestAction); + ts.assertValueCount(1); + ts.assertTerminalEvent(); + ts.assertNoErrors(); + } + } + private static class TestObserver extends Subscriber> { boolean onCompleted; diff --git a/src/test/java/rx/internal/operators/OperatorMergeDelayErrorTest.java b/src/test/java/rx/internal/operators/OperatorMergeDelayErrorTest.java index 7943e30dee..05326cf75a 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeDelayErrorTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeDelayErrorTest.java @@ -55,8 +55,8 @@ public void before() { @Test public void testErrorDelayed1() { - final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Observable o2 = Observable.create(new TestErrorObservable("one", "two", "three")); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); Observable m = Observable.mergeDelayError(o1, o2); m.subscribe(stringObserver); @@ -76,10 +76,10 @@ public void testErrorDelayed1() { @Test public void testErrorDelayed2() { - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null)); - final Observable o4 = Observable.create(new TestErrorObservable("nine")); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null)); + final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine")); Observable m = Observable.mergeDelayError(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -101,10 +101,10 @@ public void testErrorDelayed2() { @Test public void testErrorDelayed3() { - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", "five", "six")); - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null)); - final Observable o4 = Observable.create(new TestErrorObservable("nine")); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("four", "five", "six")); + final Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null)); + final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine")); Observable m = Observable.mergeDelayError(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -124,10 +124,10 @@ public void testErrorDelayed3() { @Test public void testErrorDelayed4() { - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", "five", "six")); - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight")); - final Observable o4 = Observable.create(new TestErrorObservable("nine", null)); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("four", "five", "six")); + final Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight")); + final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine", null)); Observable m = Observable.mergeDelayError(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -153,7 +153,7 @@ public void testErrorDelayed4WithThreading() { // throw the error at the very end so no onComplete will be called after it final TestAsyncErrorObservable o4 = new TestAsyncErrorObservable("nine", null); - Observable m = Observable.mergeDelayError(Observable.create(o1), Observable.create(o2), Observable.create(o3), Observable.create(o4)); + Observable m = Observable.mergeDelayError(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2), Observable.unsafeCreate(o3), Observable.unsafeCreate(o4)); m.subscribe(stringObserver); try { @@ -180,8 +180,8 @@ public void testErrorDelayed4WithThreading() { @Test public void testCompositeErrorDelayed1() { - final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Observable o2 = Observable.create(new TestErrorObservable("one", "two", null)); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("one", "two", null)); Observable m = Observable.mergeDelayError(o1, o2); m.subscribe(stringObserver); @@ -200,8 +200,8 @@ public void testCompositeErrorDelayed1() { @Test public void testCompositeErrorDelayed2() { - final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Observable o2 = Observable.create(new TestErrorObservable("one", "two", null)); + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("one", "two", null)); Observable m = Observable.mergeDelayError(o1, o2); CaptureObserver w = new CaptureObserver(); @@ -223,10 +223,10 @@ public void testCompositeErrorDelayed2() { @Test public void testMergeObservableOfObservables() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { @@ -247,8 +247,8 @@ public void call(Subscriber> observer) { @Test public void testMergeArray() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); Observable m = Observable.mergeDelayError(o1, o2); m.subscribe(stringObserver); @@ -260,8 +260,8 @@ public void testMergeArray() { @Test public void testMergeList() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); List> listOfObservables = new ArrayList>(); listOfObservables.add(o1); listOfObservables.add(o2); @@ -277,8 +277,8 @@ public void testMergeList() { // This is pretty much a clone of testMergeList but with the overloaded MergeDelayError for Iterables @Test public void mergeIterable() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); List> listOfObservables = new ArrayList>(); listOfObservables.add(o1); listOfObservables.add(o2); @@ -296,7 +296,7 @@ public void testMergeArrayWithThreading() { final TestASynchronousObservable o1 = new TestASynchronousObservable(); final TestASynchronousObservable o2 = new TestASynchronousObservable(); - Observable m = Observable.mergeDelayError(Observable.create(o1), Observable.create(o2)); + Observable m = Observable.mergeDelayError(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)); m.subscribe(stringObserver); try { @@ -455,7 +455,7 @@ public void onNext(String args) { } @Test public void testMergeSourceWhichDoesntPropagateExceptionBack() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { try { @@ -527,11 +527,11 @@ public void testErrorInParentObservableDelayed() throws Exception { for (int i = 0; i < 50; i++) { final TestASynchronous1sDelayedObservable o1 = new TestASynchronous1sDelayedObservable(); final TestASynchronous1sDelayedObservable o2 = new TestASynchronous1sDelayedObservable(); - Observable> parentObservable = Observable.create(new Observable.OnSubscribe>() { + Observable> parentObservable = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> op) { - op.onNext(Observable.create(o1)); - op.onNext(Observable.create(o2)); + op.onNext(Observable.unsafeCreate(o1)); + op.onNext(Observable.unsafeCreate(o2)); op.onError(new NullPointerException("throwing exception in parent")); } }); diff --git a/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java b/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java index 56685b1630..5be03a3b6f 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeMaxConcurrentTest.java @@ -72,7 +72,7 @@ public void testMaxConcurrent() { for (int i = 0; i < observableCount; i++) { SubscriptionCheckObservable sco = new SubscriptionCheckObservable(subscriptionCount, maxConcurrent); scos.add(sco); - os.add(Observable.create(sco)); + os.add(Observable.unsafeCreate(sco)); } Iterator iter = Observable.merge(os, maxConcurrent).toBlocking().toIterable().iterator(); @@ -197,7 +197,7 @@ public void testSimpleAsyncLoop() { testSimpleAsync(); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleAsync() { for (int i = 1; i < 50; i++) { TestSubscriber ts = new TestSubscriber(); @@ -217,7 +217,7 @@ public void testSimpleAsync() { assertEquals(expected, actual); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleOneLessAsyncLoop() { int max = 200; if (PlatformDependent.isAndroid()) { @@ -227,7 +227,7 @@ public void testSimpleOneLessAsyncLoop() { testSimpleOneLessAsync(); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleOneLessAsync() { long t = System.currentTimeMillis(); for (int i = 2; i < 50; i++) { diff --git a/src/test/java/rx/internal/operators/OperatorMergeTest.java b/src/test/java/rx/internal/operators/OperatorMergeTest.java index d79649667d..bc13673f5e 100644 --- a/src/test/java/rx/internal/operators/OperatorMergeTest.java +++ b/src/test/java/rx/internal/operators/OperatorMergeTest.java @@ -53,10 +53,10 @@ public void before() { @Test public void testMergeObservableOfObservables() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); - Observable> observableOfObservables = Observable.create(new Observable.OnSubscribe>() { + Observable> observableOfObservables = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { @@ -77,8 +77,8 @@ public void call(Subscriber> observer) { @Test public void testMergeArray() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); Observable m = Observable.merge(o1, o2); m.subscribe(stringObserver); @@ -90,8 +90,8 @@ public void testMergeArray() { @Test public void testMergeList() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); + final Observable o1 = Observable.unsafeCreate(new TestSynchronousObservable()); + final Observable o2 = Observable.unsafeCreate(new TestSynchronousObservable()); List> listOfObservables = new ArrayList>(); listOfObservables.add(o1); listOfObservables.add(o2); @@ -110,7 +110,7 @@ public void testUnSubscribeObservableOfObservables() throws InterruptedException final AtomicBoolean unsubscribed = new AtomicBoolean(); final CountDownLatch latch = new CountDownLatch(1); - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(final Subscriber> observer) { @@ -172,7 +172,7 @@ public void testMergeArrayWithThreading() { final TestASynchronousObservable o1 = new TestASynchronousObservable(); final TestASynchronousObservable o2 = new TestASynchronousObservable(); - Observable m = Observable.merge(Observable.create(o1), Observable.create(o2)); + Observable m = Observable.merge(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)); TestSubscriber ts = new TestSubscriber(stringObserver); m.subscribe(ts); @@ -195,7 +195,7 @@ public void testSynchronizationOfMultipleSequences() throws Throwable { final AtomicInteger concurrentCounter = new AtomicInteger(); final AtomicInteger totalCounter = new AtomicInteger(); - Observable m = Observable.merge(Observable.create(o1), Observable.create(o2)); + Observable m = Observable.merge(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)); m.subscribe(new Subscriber() { @Override @@ -262,8 +262,8 @@ public void onNext(String v) { @Test public void testError1() { // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior - final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" - final Observable o2 = Observable.create(new TestErrorObservable("one", "two", "three")); // we expect to lose all of these since o1 is done first and fails + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); // we expect to lose all of these since o1 is done first and fails Observable m = Observable.merge(o1, o2); m.subscribe(stringObserver); @@ -284,10 +284,10 @@ public void testError1() { @Test public void testError2() { // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails - final Observable o4 = Observable.create(new TestErrorObservable("nine"));// we expect to lose all of these since o2 is done first and fails + final Observable o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six" + final Observable o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails + final Observable o4 = Observable.unsafeCreate(new TestErrorObservable("nine"));// we expect to lose all of these since o2 is done first and fails Observable m = Observable.merge(o1, o2, o3, o4); m.subscribe(stringObserver); @@ -308,7 +308,7 @@ public void testError2() { @Test public void testThrownErrorHandling() { TestSubscriber ts = new TestSubscriber(); - Observable o1 = Observable.create(new OnSubscribe() { + Observable o1 = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -460,7 +460,7 @@ public void testEarlyUnsubscribe() { } private Observable createObservableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(final Scheduler scheduler, final AtomicBoolean unsubscribed) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -499,7 +499,7 @@ public void testConcurrency() { @Test public void testConcurrencyWithSleeping() { - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber s) { @@ -543,7 +543,7 @@ public void call() { @Test public void testConcurrencyWithBrokenOnCompleteContract() { - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber s) { @@ -807,7 +807,7 @@ public void mergeWithNullValues() { public void mergeWithTerminalEventAfterUnsubscribe() { System.out.println("mergeWithTerminalEventAfterUnsubscribe"); TestSubscriber ts = new TestSubscriber(); - Observable bad = Observable.create(new OnSubscribe() { + Observable bad = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -984,7 +984,7 @@ public void mergeManyAsyncSingle() { @Override public Observable call(final Integer i) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -1205,6 +1205,34 @@ public void onNext(Integer t) { assertTrue(latch.await(10, TimeUnit.SECONDS)); } + @Test + public void testConcurrentMergeInnerError() { + for (int i = 0; i < 1000; i++) { + final TestSubscriber ts = TestSubscriber.create(); + final PublishSubject ps1 = PublishSubject.create(); + final PublishSubject ps2 = PublishSubject.create(); + final Exception error = new NullPointerException(); + Action0 action1 = new Action0() { + @Override + public void call() { + ps1.onNext(1); + ps1.onCompleted(); + } + }; + Action0 action2 = new Action0() { + @Override + public void call() { + ps2.onError(error); + } + }; + + Observable.mergeDelayError(ps1, ps2).subscribe(ts); + TestUtil.race(action1, action2); + ts.assertTerminalEvent(); + ts.assertError(error); + } + } + private static Action1 printCount() { return new Action1() { long count; diff --git a/src/test/java/rx/internal/operators/OperatorObserveOnTest.java b/src/test/java/rx/internal/operators/OperatorObserveOnTest.java index c5a29e04a2..df2e21e5c8 100644 --- a/src/test/java/rx/internal/operators/OperatorObserveOnTest.java +++ b/src/test/java/rx/internal/operators/OperatorObserveOnTest.java @@ -526,7 +526,7 @@ public boolean hasNext() { @Test public void testQueueFullEmitsError() { final CountDownLatch latch = new CountDownLatch(1); - Observable observable = Observable.create(new OnSubscribe() { + Observable observable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { diff --git a/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java b/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java index ea48f2847e..b76405b12c 100644 --- a/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnBackpressureBufferTest.java @@ -227,7 +227,7 @@ public void onNext(T t) { }); } - static final Observable infinite = Observable.create(new OnSubscribe() { + static final Observable infinite = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { diff --git a/src/test/java/rx/internal/operators/OperatorOnBackpressureDropTest.java b/src/test/java/rx/internal/operators/OperatorOnBackpressureDropTest.java index 87d748e2ea..07174570b5 100644 --- a/src/test/java/rx/internal/operators/OperatorOnBackpressureDropTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnBackpressureDropTest.java @@ -151,7 +151,7 @@ public void testOnDropMethodIsCalled() { final List list = new ArrayList(); // request 0 TestSubscriber ts = TestSubscriber.create(0); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -180,7 +180,7 @@ public void call(Integer t) { public void testUpstreamEmitsOnCompletedAfterFailureWithoutCheckingSubscription() { TestSubscriber ts = TestSubscriber.create(0); final RuntimeException e = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -221,7 +221,7 @@ public void call(Throwable t) { TestSubscriber ts = TestSubscriber.create(0); final RuntimeException e1 = new RuntimeException(); final RuntimeException e2 = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -255,7 +255,7 @@ public void call(Integer t) { public void testUpstreamEmitsOnNextAfterFailureWithoutCheckingSubscription() { TestSubscriber ts = TestSubscriber.create(0); final RuntimeException e = new RuntimeException(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber sub) { @@ -291,7 +291,7 @@ public void call(Long n) { } }; - static final Observable infinite = Observable.create(new OnSubscribe() { + static final Observable infinite = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -304,7 +304,7 @@ public void call(Subscriber s) { }); private static final Observable range(final long n) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { diff --git a/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunctionTest.java b/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunctionTest.java index 9076e2f542..e127334588 100644 --- a/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunctionTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaFunctionTest.java @@ -44,7 +44,7 @@ public class OperatorOnErrorResumeNextViaFunctionTest { @Test public void testResumeNextWithSynchronousExecution() { final AtomicReference receivedException = new AtomicReference(); - Observable w = Observable.create(new Observable.OnSubscribe() { + Observable w = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { @@ -94,7 +94,7 @@ public Observable call(Throwable t1) { } }; - Observable observable = Observable.create(w).onErrorResumeNext(resume); + Observable observable = Observable.unsafeCreate(w).onErrorResumeNext(resume); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @@ -131,7 +131,7 @@ public Observable call(Throwable t1) { } }; - Observable observable = Observable.create(w).onErrorResumeNext(resume); + Observable observable = Observable.unsafeCreate(w).onErrorResumeNext(resume); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); diff --git a/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaObservableTest.java b/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaObservableTest.java index a7315a9158..41f910c2fc 100644 --- a/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaObservableTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnErrorResumeNextViaObservableTest.java @@ -42,7 +42,7 @@ public void testResumeNext() { Subscription s = mock(Subscription.class); // Trigger failure on second element TestObservable f = new TestObservable(s, "one", "fail", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onErrorResumeNext(resume); @@ -72,7 +72,7 @@ public void testMapResumeAsyncNext() { Observable w = Observable.just("one", "fail", "two", "three", "fail"); // Resume Observable is async TestObservable f = new TestObservable(sr, "twoResume", "threeResume"); - Observable resume = Observable.create(f); + Observable resume = Observable.unsafeCreate(f); // Introduce map function that fails intermittently (Map does not prevent this when the observer is a // rx.operator incl onErrorResumeNextViaObservable) @@ -110,7 +110,7 @@ public String call(String s) { @Test public void testResumeNextWithFailedOnSubscribe() { - Observable testObservable = Observable.create(new OnSubscribe() { + Observable testObservable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { @@ -132,7 +132,7 @@ public void call(Subscriber t1) { @Test public void testResumeNextWithFailedOnSubscribeAsync() { - Observable testObservable = Observable.create(new OnSubscribe() { + Observable testObservable = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { diff --git a/src/test/java/rx/internal/operators/OperatorOnErrorReturnTest.java b/src/test/java/rx/internal/operators/OperatorOnErrorReturnTest.java index 57a16e7829..196d5d49ce 100644 --- a/src/test/java/rx/internal/operators/OperatorOnErrorReturnTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnErrorReturnTest.java @@ -41,7 +41,7 @@ public class OperatorOnErrorReturnTest { @Test public void testResumeNext() { TestObservable f = new TestObservable("one"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); final AtomicReference capturedException = new AtomicReference(); Observable observable = w.onErrorReturn(new Func1() { @@ -77,7 +77,7 @@ public String call(Throwable e) { @Test public void testFunctionThrowsError() { TestObservable f = new TestObservable("one"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); final AtomicReference capturedException = new AtomicReference(); Observable observable = w.onErrorReturn(new Func1() { diff --git a/src/test/java/rx/internal/operators/OperatorOnExceptionResumeNextViaObservableTest.java b/src/test/java/rx/internal/operators/OperatorOnExceptionResumeNextViaObservableTest.java index 3b403f90e1..b54c7d4c46 100644 --- a/src/test/java/rx/internal/operators/OperatorOnExceptionResumeNextViaObservableTest.java +++ b/src/test/java/rx/internal/operators/OperatorOnExceptionResumeNextViaObservableTest.java @@ -35,7 +35,7 @@ public class OperatorOnExceptionResumeNextViaObservableTest { public void testResumeNextWithException() { // Trigger failure on second element TestObservable f = new TestObservable("one", "EXCEPTION", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onExceptionResumeNext(resume); @@ -63,7 +63,7 @@ public void testResumeNextWithException() { public void testResumeNextWithRuntimeException() { // Trigger failure on second element TestObservable f = new TestObservable("one", "RUNTIMEEXCEPTION", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onExceptionResumeNext(resume); @@ -91,7 +91,7 @@ public void testResumeNextWithRuntimeException() { public void testThrowablePassesThru() { // Trigger failure on second element TestObservable f = new TestObservable("one", "THROWABLE", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onExceptionResumeNext(resume); @@ -119,7 +119,7 @@ public void testThrowablePassesThru() { public void testErrorPassesThru() { // Trigger failure on second element TestObservable f = new TestObservable("one", "ON_OVERFLOW_ERROR", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); Observable resume = Observable.just("twoResume", "threeResume"); Observable observable = w.onExceptionResumeNext(resume); @@ -149,7 +149,7 @@ public void testMapResumeAsyncNext() { Observable w = Observable.just("one", "fail", "two", "three", "fail"); // Resume Observable is async TestObservable f = new TestObservable("twoResume", "threeResume"); - Observable resume = Observable.create(f); + Observable resume = Observable.unsafeCreate(f); // Introduce map function that fails intermittently (Map does not prevent this when the observer is a // rx.operator incl onErrorResumeNextViaObservable) diff --git a/src/test/java/rx/internal/operators/OperatorPublishTest.java b/src/test/java/rx/internal/operators/OperatorPublishTest.java index d0f524159f..9855143447 100644 --- a/src/test/java/rx/internal/operators/OperatorPublishTest.java +++ b/src/test/java/rx/internal/operators/OperatorPublishTest.java @@ -37,7 +37,7 @@ public class OperatorPublishTest { @Test public void testPublish() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - ConnectableObservable o = Observable.create(new OnSubscribe() { + ConnectableObservable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -368,7 +368,7 @@ public void onStart() { @Test public void testConnectIsIdempotent() { final AtomicInteger calls = new AtomicInteger(); - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { calls.getAndIncrement(); diff --git a/src/test/java/rx/internal/operators/OperatorRepeatTest.java b/src/test/java/rx/internal/operators/OperatorRepeatTest.java index c736b2ef3f..1a37f23c05 100644 --- a/src/test/java/rx/internal/operators/OperatorRepeatTest.java +++ b/src/test/java/rx/internal/operators/OperatorRepeatTest.java @@ -40,7 +40,7 @@ public class OperatorRepeatTest { public void testRepetition() { int NUM = 10; final AtomicInteger count = new AtomicInteger(); - int value = Observable.create(new OnSubscribe() { + int value = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber o) { @@ -68,7 +68,7 @@ public void testNoStackOverFlow() { public void testRepeatTakeWithSubscribeOn() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable oi = Observable.create(new OnSubscribe() { + Observable oi = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { diff --git a/src/test/java/rx/internal/operators/OperatorReplayTest.java b/src/test/java/rx/internal/operators/OperatorReplayTest.java index e98c542cd8..fbe256ef01 100644 --- a/src/test/java/rx/internal/operators/OperatorReplayTest.java +++ b/src/test/java/rx/internal/operators/OperatorReplayTest.java @@ -915,7 +915,7 @@ public void testColdReplayBackpressure() { @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(new Observable.OnSubscribe() { + Observable o = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -1055,7 +1055,7 @@ public void testNoMissingBackpressureException() { m = 4 * 1000 * 1000; } - Observable firehose = Observable.create(new OnSubscribe() { + Observable firehose = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { for (int i = 0; i < m; i++) { @@ -1577,4 +1577,20 @@ public ConnectableObservable call(Observable o) { }); } + @Test + public void noOldEntries() { + TestScheduler scheduler = new TestScheduler(); + + Observable source = Observable.just(1) + .replay(2, TimeUnit.SECONDS, scheduler) + .autoConnect(); + + source.test().assertResult(1); + + source.test().assertResult(1); + + scheduler.advanceTimeBy(3, TimeUnit.SECONDS); + + source.test().assertResult(); + } } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorRetryTest.java b/src/test/java/rx/internal/operators/OperatorRetryTest.java index 7937f8945f..d4cf161dc5 100644 --- a/src/test/java/rx/internal/operators/OperatorRetryTest.java +++ b/src/test/java/rx/internal/operators/OperatorRetryTest.java @@ -45,7 +45,7 @@ public class OperatorRetryTest { public void iterativeBackoff() { @SuppressWarnings("unchecked") Observer consumer = mock(Observer.class); - Observable producer = Observable.create(new OnSubscribe() { + Observable producer = Observable.unsafeCreate(new OnSubscribe() { private AtomicInteger count = new AtomicInteger(4); long last = System.currentTimeMillis(); @@ -116,7 +116,7 @@ public void testRetryIndefinitely() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); int NUM_RETRIES = 20; - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); origin.retry().unsafeSubscribe(new TestSubscriber(observer)); InOrder inOrder = inOrder(observer); @@ -136,7 +136,7 @@ public void testSchedulingNotificationHandler() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); int NUM_RETRIES = 2; - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); TestSubscriber subscriber = new TestSubscriber(observer); origin.retryWhen(new Func1, Observable>() { @Override @@ -168,7 +168,7 @@ public void testOnNextFromNotificationHandler() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); int NUM_RETRIES = 2; - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); origin.retryWhen(new Func1, Observable>() { @Override public Observable call(Observable t1) { @@ -198,7 +198,7 @@ public Void call(Throwable t1) { public void testOnCompletedFromNotificationHandler() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(1)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(1)); TestSubscriber subscriber = new TestSubscriber(observer); origin.retryWhen(new Func1, Observable>() { @Override @@ -219,7 +219,7 @@ public Observable call(Observable t1) { public void testOnErrorFromNotificationHandler() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(2)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(2)); origin.retryWhen(new Func1, Observable>() { @Override public Observable call(Observable t1) { @@ -247,7 +247,7 @@ public void call(Subscriber subscriber) { } }; - int first = Observable.create(onSubscribe) + int first = Observable.unsafeCreate(onSubscribe) .retryWhen(new Func1, Observable>() { @Override public Observable call(Observable attempt) { @@ -270,7 +270,7 @@ public Void call(Throwable o, Integer integer) { public void testOriginFails() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(1)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(1)); origin.subscribe(observer); InOrder inOrder = inOrder(observer); @@ -286,7 +286,7 @@ public void testRetryFail() { int NUM_FAILURES = 2; @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); origin.retry(NUM_RETRIES).subscribe(observer); InOrder inOrder = inOrder(observer); @@ -305,7 +305,7 @@ public void testRetrySuccess() { int NUM_FAILURES = 1; @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); origin.retry(3).subscribe(observer); InOrder inOrder = inOrder(observer); @@ -325,7 +325,7 @@ public void testInfiniteRetry() { int NUM_FAILURES = 20; @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_FAILURES)); origin.retry().subscribe(observer); InOrder inOrder = inOrder(observer); @@ -478,7 +478,7 @@ public boolean isUnsubscribed() { }); } }; - Observable stream = Observable.create(onSubscribe); + Observable stream = Observable.unsafeCreate(onSubscribe); Observable streamWithRetry = stream.retry(); Subscription sub = streamWithRetry.subscribe(); assertEquals(1, subsCount.get()); @@ -512,7 +512,7 @@ public void call(Subscriber s) { } }; - Observable.create(onSubscribe).retry(3).subscribe(ts); + Observable.unsafeCreate(onSubscribe).retry(3).subscribe(ts); assertEquals(4, subsCount.get()); // 1 + 3 retries } @@ -530,7 +530,7 @@ public void call(Subscriber s) { } }; - Observable.create(onSubscribe).retry(1).subscribe(ts); + Observable.unsafeCreate(onSubscribe).retry(1).subscribe(ts); assertEquals(2, subsCount.get()); } @@ -548,7 +548,7 @@ public void call(Subscriber s) { } }; - Observable.create(onSubscribe).retry(0).subscribe(ts); + Observable.unsafeCreate(onSubscribe).retry(0).subscribe(ts); assertEquals(1, subsCount.get()); } @@ -651,7 +651,7 @@ public void testUnsubscribeAfterError() { // Observable that always fails after 100ms SlowObservable so = new SlowObservable(100, 0); - Observable o = Observable.create(so).retry(5); + Observable o = Observable.unsafeCreate(so).retry(5); AsyncObserver async = new AsyncObserver(observer); @@ -676,7 +676,7 @@ public void testTimeoutWithRetry() { // Observable that sends every 100ms (timeout fails instead) SlowObservable so = new SlowObservable(100, 10); - Observable o = Observable.create(so).timeout(80, TimeUnit.MILLISECONDS).retry(5); + Observable o = Observable.unsafeCreate(so).timeout(80, TimeUnit.MILLISECONDS).retry(5); AsyncObserver async = new AsyncObserver(observer); @@ -700,7 +700,7 @@ public void testRetryWithBackpressure() throws InterruptedException { for (int i = 0; i < 400; i++) { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); TestSubscriber ts = new TestSubscriber(observer); origin.retry().observeOn(Schedulers.computation()).unsafeSubscribe(ts); ts.awaitTerminalEvent(5, TimeUnit.SECONDS); @@ -743,7 +743,7 @@ public void testRetryWithBackpressureParallel() throws InterruptedException { public void run() { final AtomicInteger nexts = new AtomicInteger(); try { - Observable origin = Observable.create(new FuncWithErrors(NUM_RETRIES)); + Observable origin = Observable.unsafeCreate(new FuncWithErrors(NUM_RETRIES)); TestSubscriber ts = new TestSubscriber(); origin.retry() .observeOn(Schedulers.computation()).unsafeSubscribe(ts); @@ -866,7 +866,7 @@ public void testIssue1900SourceNotSupportingBackpressure() { final int NUM_MSG = 1034; final AtomicInteger count = new AtomicInteger(); - Observable origin = Observable.create(new Observable.OnSubscribe() { + Observable origin = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber o) { diff --git a/src/test/java/rx/internal/operators/OperatorRetryWithPredicateTest.java b/src/test/java/rx/internal/operators/OperatorRetryWithPredicateTest.java index f526d327f6..dab59e9f0d 100644 --- a/src/test/java/rx/internal/operators/OperatorRetryWithPredicateTest.java +++ b/src/test/java/rx/internal/operators/OperatorRetryWithPredicateTest.java @@ -81,7 +81,7 @@ public void testWithNothingToRetry() { } @Test public void testRetryTwice() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { int count; @Override public void call(Subscriber t1) { @@ -116,7 +116,7 @@ public void call(Subscriber t1) { } @Test public void testRetryTwiceAndGiveUp() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { t1.onNext(0); @@ -143,7 +143,7 @@ public void call(Subscriber t1) { } @Test public void testRetryOnSpecificException() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { int count; @Override public void call(Subscriber t1) { @@ -179,7 +179,7 @@ public void call(Subscriber t1) { public void testRetryOnSpecificExceptionAndNotOther() { final IOException ioe = new IOException(); final TestException te = new TestException(); - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { int count; @Override public void call(Subscriber t1) { @@ -238,7 +238,7 @@ public void testUnsubscribeAfterError() { // Observable that always fails after 100ms OperatorRetryTest.SlowObservable so = new OperatorRetryTest.SlowObservable(100, 0); Observable o = Observable - .create(so) + .unsafeCreate(so) .retry(retry5); OperatorRetryTest.AsyncObserver async = new OperatorRetryTest.AsyncObserver(observer); @@ -265,7 +265,7 @@ public void testTimeoutWithRetry() { // Observable that sends every 100ms (timeout fails instead) OperatorRetryTest.SlowObservable so = new OperatorRetryTest.SlowObservable(100, 10); Observable o = Observable - .create(so) + .unsafeCreate(so) .timeout(80, TimeUnit.MILLISECONDS) .retry(retry5); diff --git a/src/test/java/rx/internal/operators/OperatorSampleTest.java b/src/test/java/rx/internal/operators/OperatorSampleTest.java index 04000973ec..20d59d8523 100644 --- a/src/test/java/rx/internal/operators/OperatorSampleTest.java +++ b/src/test/java/rx/internal/operators/OperatorSampleTest.java @@ -50,7 +50,7 @@ public void before() { @Test public void testSample() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer1) { innerScheduler.schedule(new Action0() { @@ -111,7 +111,7 @@ public void call() { @Test public void sampleWithTimeEmitAndTerminate() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer1) { innerScheduler.schedule(new Action0() { @@ -303,7 +303,7 @@ public void sampleWithSamplerThrows() { @Test public void testSampleUnsubscribe() { final Subscription s = mock(Subscription.class); - Observable o = Observable.create( + Observable o = Observable.unsafeCreate( new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -387,7 +387,7 @@ public void dontUnsubscribeChild2() { @Test public void neverSetProducer() { - Observable neverBackpressure = Observable.create(new OnSubscribe() { + Observable neverBackpressure = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.setProducer(new Producer() { @@ -430,7 +430,7 @@ public void setProducer(Producer p) { public void unsubscribeMainAfterCompleted() { final AtomicBoolean unsubscribed = new AtomicBoolean(); - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.add(Subscriptions.create(new Action0() { @@ -467,7 +467,7 @@ public void onCompleted() { public void unsubscribeSamplerAfterCompleted() { final AtomicBoolean unsubscribed = new AtomicBoolean(); - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.add(Subscriptions.create(new Action0() { diff --git a/src/test/java/rx/internal/operators/OperatorScanTest.java b/src/test/java/rx/internal/operators/OperatorScanTest.java index c6bf08b718..e20519ed17 100644 --- a/src/test/java/rx/internal/operators/OperatorScanTest.java +++ b/src/test/java/rx/internal/operators/OperatorScanTest.java @@ -306,7 +306,7 @@ public Integer call(Integer t1, Integer t2) { @Test public void testScanShouldNotRequestZero() { final AtomicReference producer = new AtomicReference(); - Observable o = Observable.create(new Observable.OnSubscribe() { + Observable o = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { Producer p = spy(new Producer() { @@ -371,7 +371,7 @@ public Integer call(Integer t1, Integer t2) { @Test public void testInitialValueEmittedWithProducer() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t) { t.setProducer(new Producer() { diff --git a/src/test/java/rx/internal/operators/OperatorSerializeTest.java b/src/test/java/rx/internal/operators/OperatorSerializeTest.java index 450e2e0185..14d38dd76b 100644 --- a/src/test/java/rx/internal/operators/OperatorSerializeTest.java +++ b/src/test/java/rx/internal/operators/OperatorSerializeTest.java @@ -51,7 +51,7 @@ public void before() { @Test public void testSingleThreadedBasic() { TestSingleThreadedObservable onSubscribe = new TestSingleThreadedObservable("one", "two", "three"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); w.serialize().subscribe(observer); onSubscribe.waitToFinish(); @@ -69,7 +69,7 @@ public void testSingleThreadedBasic() { @Test public void testMultiThreadedBasic() { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyobserver = new BusyObserver(); @@ -92,7 +92,7 @@ public void testMultiThreadedBasic() { @Test public void testMultiThreadedWithNPE() { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three", null); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyobserver = new BusyObserver(); @@ -123,7 +123,7 @@ public void testMultiThreadedWithNPEinMiddle() { boolean lessThan9 = false; for (int i = 0; i < 3; i++) { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three", null, "four", "five", "six", "seven", "eight", "nine"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyobserver = new BusyObserver(); diff --git a/src/test/java/rx/internal/operators/OperatorSubscribeOnTest.java b/src/test/java/rx/internal/operators/OperatorSubscribeOnTest.java index 65f1b9870e..b28b3fc27b 100644 --- a/src/test/java/rx/internal/operators/OperatorSubscribeOnTest.java +++ b/src/test/java/rx/internal/operators/OperatorSubscribeOnTest.java @@ -26,16 +26,12 @@ import org.junit.Test; -import rx.Observable; +import rx.*; import rx.Observable.OnSubscribe; import rx.Observable.Operator; -import rx.Observer; -import rx.Producer; -import rx.Scheduler; -import rx.Subscriber; -import rx.Subscription; -import rx.functions.Action0; -import rx.observers.TestSubscriber; +import rx.functions.*; +import rx.internal.util.*; +import rx.observers.*; import rx.schedulers.Schedulers; public class OperatorSubscribeOnTest { @@ -50,7 +46,7 @@ public void testIssue813() throws InterruptedException { TestSubscriber observer = new TestSubscriber(); final Subscription subscription = Observable - .create(new Observable.OnSubscribe() { + .unsafeCreate(new Observable.OnSubscribe() { @Override public void call( final Subscriber subscriber) { @@ -85,7 +81,7 @@ public void call( @Test public void testThrownErrorHandling() { TestSubscriber ts = new TestSubscriber(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -100,7 +96,7 @@ public void call(Subscriber s) { @Test public void testOnError() { TestSubscriber ts = new TestSubscriber(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -170,7 +166,7 @@ public Subscription schedule(final Action0 action, final long delayTime, final T public void testUnsubscribeInfiniteStream() throws InterruptedException { TestSubscriber ts = new TestSubscriber(); final AtomicInteger count = new AtomicInteger(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber sub) { @@ -267,4 +263,85 @@ public void onNext(Integer t) { ts.assertNoErrors(); } + @Test + public void noSamepoolDeadlock() { + final int n = 4 * RxRingBuffer.SIZE; + + Observable.create(new Action1>() { + @Override + public void call(Emitter e) { + for (int i = 0; i < n; i++) { + e.onNext(i); + try { + Thread.sleep(1); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + e.onCompleted(); + } + }, Emitter.BackpressureMode.DROP) + .map(UtilityFunctions.identity()) + .subscribeOn(Schedulers.io(), false) + .observeOn(Schedulers.computation()) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertValueCount(n) + .assertNoErrors() + .assertCompleted(); + } + + @Test + public void noSamepoolDeadlockRequestOn() { + final int n = 4 * RxRingBuffer.SIZE; + + Observable.create(new Action1>() { + @Override + public void call(Emitter e) { + for (int i = 0; i < n; i++) { + e.onNext(i); + try { + Thread.sleep(1); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + e.onCompleted(); + } + }, Emitter.BackpressureMode.DROP) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.computation()) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertValueCount(n) + .assertNoErrors() + .assertCompleted(); + } + + @Test + public void noSamepoolDeadlockRequestOn2() { + final int n = 4 * RxRingBuffer.SIZE; + + Observable.create(new Action1>() { + @Override + public void call(Emitter e) { + for (int i = 0; i < n; i++) { + e.onNext(i); + try { + Thread.sleep(1); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + e.onCompleted(); + } + }, Emitter.BackpressureMode.DROP) + .subscribeOn(Schedulers.io(), true) + .observeOn(Schedulers.computation()) + .test() + .awaitTerminalEvent(5, TimeUnit.SECONDS) + .assertValueCount(RxRingBuffer.SIZE) + .assertNoErrors() + .assertCompleted(); + } } diff --git a/src/test/java/rx/internal/operators/OperatorSwitchIfEmptyTest.java b/src/test/java/rx/internal/operators/OperatorSwitchIfEmptyTest.java index 70225a53eb..235cac495b 100644 --- a/src/test/java/rx/internal/operators/OperatorSwitchIfEmptyTest.java +++ b/src/test/java/rx/internal/operators/OperatorSwitchIfEmptyTest.java @@ -17,16 +17,15 @@ import static org.junit.Assert.*; -import java.util.*; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import rx.*; -import rx.Observable; import rx.Observable.OnSubscribe; -import rx.functions.Action0; +import rx.functions.*; import rx.observers.TestSubscriber; import rx.schedulers.Schedulers; import rx.subscriptions.Subscriptions; @@ -58,7 +57,7 @@ public void testSwitchWhenEmpty() throws Exception { @Test public void testSwitchWithProducer() throws Exception { final AtomicBoolean emitted = new AtomicBoolean(false); - Observable withProducer = Observable.create(new Observable.OnSubscribe() { + Observable withProducer = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { subscriber.setProducer(new Producer() { @@ -82,7 +81,7 @@ public void request(long n) { public void testSwitchTriggerUnsubscribe() throws Exception { final Subscription empty = Subscriptions.empty(); - Observable withProducer = Observable.create(new Observable.OnSubscribe() { + Observable withProducer = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { subscriber.add(empty); @@ -121,7 +120,7 @@ public void onNext(Long aLong) { public void testSwitchShouldTriggerUnsubscribe() { final Subscription s = Subscriptions.empty(); - Observable.create(new Observable.OnSubscribe() { + Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber subscriber) { subscriber.add(s); @@ -176,7 +175,7 @@ public void testBackpressureOnFirstObservable() { @Test(timeout = 10000) public void testRequestsNotLost() throws InterruptedException { final TestSubscriber ts = new TestSubscriber(0); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber subscriber) { @@ -212,4 +211,29 @@ public void call() { public void testAlternateNull() { Observable.just(1).switchIfEmpty(null); } + + Observable recursiveSwitch(final int level) { + if (level == 100) { + return Observable.just(Thread.currentThread().getStackTrace()); + } + return Observable.empty().switchIfEmpty(Observable.defer(new Func0>() { + @Override + public Observable call() { + return recursiveSwitch(level + 1); + } + })); + } + + @Test + public void stackDepth() { + StackTraceElement[] trace = recursiveSwitch(0) + .toBlocking().last(); + + if (trace.length > 1000 || trace.length < 100) { + for (StackTraceElement ste : trace) { + System.out.println(ste); + } + fail("Stack too deep: " + trace.length); + } + } } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorSwitchTest.java b/src/test/java/rx/internal/operators/OperatorSwitchTest.java index 1a8732e341..00887db5e1 100644 --- a/src/test/java/rx/internal/operators/OperatorSwitchTest.java +++ b/src/test/java/rx/internal/operators/OperatorSwitchTest.java @@ -53,10 +53,10 @@ public void before() { @Test public void testSwitchWhenOuterCompleteBeforeInner() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 70, "one"); @@ -80,10 +80,10 @@ public void call(Subscriber observer) { @Test public void testSwitchWhenInnerCompleteBeforeOuter() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 10, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 10, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 0, "one"); @@ -92,7 +92,7 @@ public void call(Subscriber observer) { } })); - publishNext(observer, 100, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 100, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 0, "three"); @@ -123,10 +123,10 @@ public void call(Subscriber observer) { @Test public void testSwitchWithComplete() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { publishNext(observer, 60, "one"); @@ -134,7 +134,7 @@ public void call(final Subscriber observer) { } })); - publishNext(observer, 200, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 200, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { publishNext(observer, 0, "three"); @@ -179,10 +179,10 @@ public void call(final Subscriber observer) { @Test public void testSwitchWithError() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { publishNext(observer, 50, "one"); @@ -190,7 +190,7 @@ public void call(final Subscriber observer) { } })); - publishNext(observer, 200, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 200, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 0, "three"); @@ -235,10 +235,10 @@ public void call(Subscriber observer) { @Test public void testSwitchWithSubsequenceComplete() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 50, "one"); @@ -246,14 +246,14 @@ public void call(Subscriber observer) { } })); - publishNext(observer, 130, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 130, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishCompleted(observer, 0); } })); - publishNext(observer, 150, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 150, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 50, "three"); @@ -285,10 +285,10 @@ public void call(Subscriber observer) { @Test public void testSwitchWithSubsequenceError() { - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 50, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 50, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 50, "one"); @@ -296,14 +296,14 @@ public void call(Subscriber observer) { } })); - publishNext(observer, 130, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 130, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishError(observer, 0, new TestException()); } })); - publishNext(observer, 150, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 150, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 50, "three"); @@ -364,10 +364,10 @@ public void call() { @Test public void testSwitchIssue737() { // https://github.com/ReactiveX/RxJava/issues/737 - Observable> source = Observable.create(new Observable.OnSubscribe>() { + Observable> source = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { - publishNext(observer, 0, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 0, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 10, "1-one"); @@ -377,7 +377,7 @@ public void call(Subscriber observer) { publishCompleted(observer, 40); } })); - publishNext(observer, 25, Observable.create(new Observable.OnSubscribe() { + publishNext(observer, 25, Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 10, "2-one"); @@ -407,7 +407,7 @@ public void call(Subscriber observer) { @Test public void testBackpressure() { - final Observable o1 = Observable.create(new Observable.OnSubscribe() { + final Observable o1 = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { observer.setProducer(new Producer() { @@ -428,7 +428,7 @@ public void request(long n) { }); } }); - final Observable o2 = Observable.create(new Observable.OnSubscribe() { + final Observable o2 = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { observer.setProducer(new Producer() { @@ -449,7 +449,7 @@ public void request(long n) { }); } }); - final Observable o3 = Observable.create(new Observable.OnSubscribe() { + final Observable o3 = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { observer.setProducer(new Producer() { @@ -469,7 +469,7 @@ public void request(long n) { }); } }); - Observable> o = Observable.create(new Observable.OnSubscribe>() { + Observable> o = Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(Subscriber> observer) { publishNext(observer, 10, o1); @@ -519,7 +519,7 @@ public void onNext(String s) { public void testUnsubscribe() { final AtomicBoolean isUnsubscribed = new AtomicBoolean(); Observable.switchOnNext( - Observable.create(new Observable.OnSubscribe>() { + Observable.unsafeCreate(new Observable.OnSubscribe>() { @Override public void call(final Subscriber> subscriber) { subscriber.onNext(Observable.just(1)); @@ -894,7 +894,7 @@ public void asyncInner() throws Throwable { .switchMap(UtilityFunctions.>identity()) .observeOn(Schedulers.computation()) .ignoreElements() - .timeout(5, TimeUnit.SECONDS) + .timeout(15, TimeUnit.SECONDS) .toBlocking() .subscribe(Actions.empty(), new Action1() { @Override diff --git a/src/test/java/rx/internal/operators/OperatorTakeTest.java b/src/test/java/rx/internal/operators/OperatorTakeTest.java index 0dc3dbc8c4..0885a8b343 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeTest.java @@ -19,7 +19,7 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; -import java.util.Arrays; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -27,10 +27,13 @@ import org.mockito.InOrder; import rx.*; +import rx.Observable; import rx.Observable.OnSubscribe; +import rx.Observer; import rx.exceptions.TestException; import rx.functions.*; import rx.observers.*; +import rx.plugins.RxJavaHooks; import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; @@ -112,7 +115,7 @@ public Integer call(Integer t1) { @Test public void testTakeDoesntLeakErrors() { - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -136,7 +139,7 @@ public void call(Subscriber observer) { public void testTakeZeroDoesntLeakError() { final AtomicBoolean subscribed = new AtomicBoolean(false); final AtomicBoolean unSubscribed = new AtomicBoolean(false); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { subscribed.set(true); @@ -173,7 +176,7 @@ public boolean isUnsubscribed() { public void testUnsubscribeAfterTake() { final Subscription s = mock(Subscription.class); TestObservableFunc f = new TestObservableFunc("one", "two", "three"); - Observable w = Observable.create(f); + Observable w = Observable.unsafeCreate(f); @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); @@ -218,7 +221,7 @@ public void call(Long l) { @Test(timeout = 2000) public void testMultiTake() { final AtomicInteger count = new AtomicInteger(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -277,7 +280,7 @@ public void run() { } } - private static Observable INFINITE_OBSERVABLE = Observable.create(new OnSubscribe() { + private static Observable INFINITE_OBSERVABLE = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber op) { @@ -311,7 +314,7 @@ public void testProducerRequestThroughTake() { TestSubscriber ts = new TestSubscriber(); ts.requestMore(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -334,7 +337,7 @@ public void testProducerRequestThroughTakeIsModified() { TestSubscriber ts = new TestSubscriber(); ts.requestMore(3); final AtomicLong requested = new AtomicLong(); - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { @@ -457,4 +460,39 @@ public void takeZero() { ts.assertCompleted(); } + @Test + public void crashReportedToHooks() { + final List errors = Collections.synchronizedList(new ArrayList()); + RxJavaHooks.setOnError(new Action1() { + @Override + public void call(Throwable error) { + errors.add(error); + } + }); + + try { + Observable.just("1") + .take(1) + .toSingle() + .subscribe( + new Action1() { + @Override + public void call(String it) { + throw new TestException("bla"); + } + }, + new Action1() { + @Override + public void call(Throwable error) { + errors.add(new AssertionError()); + } + } + ); + + assertEquals("" + errors, 1, errors.size()); + assertTrue("" + errors.get(0), errors.get(0).getMessage().equals("bla")); + } finally { + RxJavaHooks.setOnError(null); + } + } } diff --git a/src/test/java/rx/internal/operators/OperatorTakeUntilTest.java b/src/test/java/rx/internal/operators/OperatorTakeUntilTest.java index 28c0c432ff..5c590c68c5 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeUntilTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeUntilTest.java @@ -40,7 +40,7 @@ public void testTakeUntil() { TestObservable other = new TestObservable(sOther); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); @@ -67,7 +67,7 @@ public void testTakeUntilSourceCompleted() { TestObservable other = new TestObservable(sOther); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); @@ -90,7 +90,7 @@ public void testTakeUntilSourceError() { Throwable error = new Throwable(); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); @@ -116,7 +116,7 @@ public void testTakeUntilOtherError() { Throwable error = new Throwable(); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); @@ -145,7 +145,7 @@ public void testTakeUntilOtherCompleted() { TestObservable other = new TestObservable(sOther); Observer result = mock(Observer.class); - Observable stringObservable = Observable.create(source).takeUntil(Observable.create(other)); + Observable stringObservable = Observable.unsafeCreate(source).takeUntil(Observable.unsafeCreate(other)); stringObservable.subscribe(result); source.sendOnNext("one"); source.sendOnNext("two"); diff --git a/src/test/java/rx/internal/operators/OperatorTakeWhileTest.java b/src/test/java/rx/internal/operators/OperatorTakeWhileTest.java index 0a3cd3ec94..2537e0e807 100644 --- a/src/test/java/rx/internal/operators/OperatorTakeWhileTest.java +++ b/src/test/java/rx/internal/operators/OperatorTakeWhileTest.java @@ -107,7 +107,7 @@ public Boolean call(String input) { @Test public void testTakeWhileDoesntLeakErrors() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { observer.onNext("one"); @@ -130,7 +130,7 @@ public void testTakeWhileProtectsPredicateCall() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable take = Observable.create(source).takeWhile(new Func1() { + Observable take = Observable.unsafeCreate(source).takeWhile(new Func1() { @Override public Boolean call(String s) { throw testException; @@ -157,7 +157,7 @@ public void testUnsubscribeAfterTake() { @SuppressWarnings("unchecked") Observer observer = mock(Observer.class); - Observable take = Observable.create(w).takeWhile(new Func1() { + Observable take = Observable.unsafeCreate(w).takeWhile(new Func1() { int index; @Override diff --git a/src/test/java/rx/internal/operators/OperatorThrottleFirstTest.java b/src/test/java/rx/internal/operators/OperatorThrottleFirstTest.java index f608cb22c6..f0fc6527a6 100644 --- a/src/test/java/rx/internal/operators/OperatorThrottleFirstTest.java +++ b/src/test/java/rx/internal/operators/OperatorThrottleFirstTest.java @@ -16,26 +16,18 @@ package rx.internal.operators; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; -import rx.Observable; +import rx.*; import rx.Observable.OnSubscribe; -import rx.Observer; -import rx.Scheduler; -import rx.Subscriber; import rx.exceptions.TestException; import rx.functions.Action0; -import rx.observers.TestSubscriber; +import rx.observers.*; import rx.schedulers.TestScheduler; import rx.subjects.PublishSubject; @@ -55,7 +47,7 @@ public void before() { @Test public void testThrottlingWithCompleted() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { publishNext(observer, 100, "one"); // publish as it's first @@ -82,7 +74,7 @@ public void call(Subscriber observer) { @Test public void testThrottlingWithError() { - Observable source = Observable.create(new OnSubscribe() { + Observable source = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { Exception error = new TestException(); @@ -214,4 +206,28 @@ public void throttleWithTestSchedulerTimeOfZero() { verify(observer).onCompleted(); verifyNoMoreInteractions(observer); } + + @Test + public void nowDrift() { + TestScheduler s = new TestScheduler(); + s.advanceTimeBy(2, TimeUnit.SECONDS); + + PublishSubject o = PublishSubject.create(); + + AssertableSubscriber as = o.throttleFirst(500, TimeUnit.MILLISECONDS, s) + .test(); + + o.onNext(1); + s.advanceTimeBy(100, TimeUnit.MILLISECONDS); + o.onNext(2); + s.advanceTimeBy(100, TimeUnit.MILLISECONDS); + o.onNext(3); + s.advanceTimeBy(-1000, TimeUnit.MILLISECONDS); + o.onNext(4); + s.advanceTimeBy(100, TimeUnit.MILLISECONDS); + o.onNext(5); + o.onCompleted(); + + as.assertResult(1, 4); + } } diff --git a/src/test/java/rx/internal/operators/OperatorTimeoutTests.java b/src/test/java/rx/internal/operators/OperatorTimeoutTests.java index 3071d78f6d..6be7706d0f 100644 --- a/src/test/java/rx/internal/operators/OperatorTimeoutTests.java +++ b/src/test/java/rx/internal/operators/OperatorTimeoutTests.java @@ -15,21 +15,26 @@ */ package rx.internal.operators; +import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import java.io.IOException; +import java.util.*; import java.util.concurrent.*; import org.junit.*; import org.mockito.*; import rx.*; +import rx.Observable; import rx.Observable.OnSubscribe; -import rx.functions.Func1; -import rx.observers.TestSubscriber; +import rx.Observer; +import rx.exceptions.TestException; +import rx.functions.*; +import rx.observers.*; import rx.schedulers.TestScheduler; -import rx.subjects.PublishSubject; +import rx.subjects.*; public class OperatorTimeoutTests { private PublishSubject underlyingSubject; @@ -234,7 +239,7 @@ public void shouldTimeoutIfSynchronizedObservableEmitFirstOnNextNotWithinTimeout @Override public void run() { - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { @@ -268,7 +273,7 @@ public void shouldUnsubscribeFromUnderlyingSubscriptionOnTimeout() throws Interr // From https://github.com/ReactiveX/RxJava/pull/951 final Subscription s = mock(Subscription.class); - Observable never = Observable.create(new OnSubscribe() { + Observable never = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.add(s); @@ -296,7 +301,7 @@ public void shouldUnsubscribeFromUnderlyingSubscriptionOnImmediatelyComplete() { // From https://github.com/ReactiveX/RxJava/pull/951 final Subscription s = mock(Subscription.class); - Observable immediatelyComplete = Observable.create(new OnSubscribe() { + Observable immediatelyComplete = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.add(s); @@ -326,7 +331,7 @@ public void shouldUnsubscribeFromUnderlyingSubscriptionOnImmediatelyErrored() th // From https://github.com/ReactiveX/RxJava/pull/951 final Subscription s = mock(Subscription.class); - Observable immediatelyError = Observable.create(new OnSubscribe() { + Observable immediatelyError = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.add(s); @@ -427,4 +432,71 @@ public void withDefaultSchedulerAndOther() { ts.assertNoErrors(); ts.assertCompleted(); } + + @Test + public void disconnectOnTimeout() { + final List list = Collections.synchronizedList(new ArrayList()); + + TestScheduler sch = new TestScheduler(); + + Subject subject = PublishSubject.create(); + Observable initialObservable = subject.share() + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Received value " + value); + return value; + } + }); + + Observable timeoutObservable = initialObservable + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Timeout received value " + value); + return value; + } + }); + + TestSubscriber subscriber = new TestSubscriber(); + initialObservable + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + list.add("Unsubscribed"); + } + }) + .timeout(1, TimeUnit.SECONDS, timeoutObservable, sch).subscribe(subscriber); + + subject.onNext(5L); + + sch.advanceTimeBy(2, TimeUnit.SECONDS); + + subject.onNext(10L); + subject.onCompleted(); + + subscriber.awaitTerminalEvent(); + subscriber.assertNoErrors(); + subscriber.assertValues(5L, 10L); + + assertEquals(Arrays.asList( + "Received value 5", + "Unsubscribed", + "Received value 10", + "Timeout received value 10" + ), list); + } + + @Test + public void fallbackIsError() { + TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.never() + .timeout(1, TimeUnit.SECONDS, Observable.error(new TestException()), sch) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailure(TestException.class); + } } diff --git a/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java b/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java index d2d0e2e567..628d1eca55 100644 --- a/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java +++ b/src/test/java/rx/internal/operators/OperatorTimeoutWithSelectorTest.java @@ -19,7 +19,8 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; -import java.util.Arrays; +import java.io.IOException; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -28,13 +29,15 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import rx.*; +import rx.Observable; import rx.Observable.OnSubscribe; +import rx.Observer; +import rx.Subscriber; import rx.exceptions.TestException; import rx.functions.*; -import rx.observers.TestSubscriber; -import rx.schedulers.Schedulers; -import rx.subjects.PublishSubject; +import rx.observers.*; +import rx.schedulers.*; +import rx.subjects.*; public class OperatorTimeoutWithSelectorTest { @Test(timeout = 2000) @@ -334,7 +337,7 @@ public void testTimeoutSelectorWithTimeoutAndOnNextRaceCondition() throws Interr public Observable call(Integer t1) { if (t1 == 1) { // Force "unsubscribe" run on another thread - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { enteredTimeoutOne.countDown(); @@ -439,4 +442,169 @@ public Observable call() { assertEquals("timeoutSelector is null", ex.getMessage()); } } + + @Test + public void disconnectOnTimeout() { + final List list = Collections.synchronizedList(new ArrayList()); + + final TestScheduler sch = new TestScheduler(); + + Subject subject = PublishSubject.create(); + Observable initialObservable = subject.share() + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Received value " + value); + return value; + } + }); + + Observable timeoutObservable = initialObservable + .map(new Func1() { + @Override + public Long call(Long value) { + list.add("Timeout received value " + value); + return value; + } + }); + + TestSubscriber subscriber = new TestSubscriber(); + initialObservable + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + list.add("Unsubscribed"); + } + }) + .timeout( + new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + new Func1>() { + @Override + public Observable call(Long v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + timeoutObservable).subscribe(subscriber); + + subject.onNext(5L); + + sch.advanceTimeBy(2, TimeUnit.SECONDS); + + subject.onNext(10L); + subject.onCompleted(); + + subscriber.awaitTerminalEvent(); + subscriber.assertNoErrors(); + subscriber.assertValues(5L, 10L); + + assertEquals(Arrays.asList( + "Received value 5", + "Unsubscribed", + "Received value 10", + "Timeout received value 10" + ), list); + } + + @Test + public void fallbackIsError() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.never() + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, Observable.error(new TestException())) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailure(TestException.class); + } + + @Test + public void mainErrors() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.error(new IOException()) + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, Observable.error(new TestException())) + .test(); + + as.assertFailure(IOException.class); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailure(IOException.class); + } + + @Test + public void timeoutCompletesWithFallback() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.never() + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch).ignoreElements(); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return Observable.timer(1, TimeUnit.SECONDS, sch); + } + }, Observable.just(1)) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertResult(1); + } + + @Test + public void nullItemTimeout() { + final TestScheduler sch = new TestScheduler(); + + AssertableSubscriber as = Observable.just(1).concatWith(Observable.never()) + .timeout(new Func0>() { + @Override + public Observable call() { + return Observable.timer(1, TimeUnit.SECONDS, sch).ignoreElements(); + } + }, + new Func1>() { + @Override + public Observable call(Object v) { + return null; + } + }, Observable.just(1)) + .test(); + + sch.advanceTimeBy(1, TimeUnit.SECONDS); + + as.assertFailureAndMessage(NullPointerException.class, "The itemTimeoutIndicator returned a null Observable", 1); + } } diff --git a/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java b/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java index 8e4317af40..c409a467bf 100644 --- a/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java +++ b/src/test/java/rx/internal/operators/OperatorUnsubscribeOnTest.java @@ -39,7 +39,7 @@ public void testUnsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnSameThread() thro try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference subscribeThread = new AtomicReference(); - Observable w = Observable.create(new OnSubscribe() { + Observable w = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { @@ -84,7 +84,7 @@ public void testUnsubscribeWhenSubscribeOnAndUnsubscribeOnAreOnDifferentThreads( try { final ThreadSubscription subscription = new ThreadSubscription(); final AtomicReference subscribeThread = new AtomicReference(); - Observable w = Observable.create(new OnSubscribe() { + Observable w = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber t1) { @@ -204,4 +204,31 @@ public Thread getThread() { } } + + @Test + public void backpressure() { + AssertableSubscriber as = Observable.range(1, 10) + .unsubscribeOn(Schedulers.trampoline()) + .test(0); + + as.assertNoValues() + .assertNoErrors() + .assertNotCompleted(); + + as.requestMore(1); + + as.assertValue(1) + .assertNoErrors() + .assertNotCompleted(); + + as.requestMore(3); + + as.assertValues(1, 2, 3, 4) + .assertNoErrors() + .assertNotCompleted(); + + as.requestMore(10); + + as.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } } \ No newline at end of file diff --git a/src/test/java/rx/internal/operators/OperatorWindowWithSizeTest.java b/src/test/java/rx/internal/operators/OperatorWindowWithSizeTest.java index 7f74cd1ee0..2e9b718e67 100644 --- a/src/test/java/rx/internal/operators/OperatorWindowWithSizeTest.java +++ b/src/test/java/rx/internal/operators/OperatorWindowWithSizeTest.java @@ -249,7 +249,7 @@ public void onCompleted() { } public static Observable hotStream() { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { while (!s.isUnsubscribed()) { diff --git a/src/test/java/rx/internal/operators/OperatorWindowWithStartEndObservableTest.java b/src/test/java/rx/internal/operators/OperatorWindowWithStartEndObservableTest.java index 858465a877..eeb47b89be 100644 --- a/src/test/java/rx/internal/operators/OperatorWindowWithStartEndObservableTest.java +++ b/src/test/java/rx/internal/operators/OperatorWindowWithStartEndObservableTest.java @@ -46,7 +46,7 @@ public void testObservableBasedOpenerAndCloser() { final List list = new ArrayList(); final List> lists = new ArrayList>(); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -58,7 +58,7 @@ public void call(Subscriber observer) { } }); - Observable openings = Observable.create(new Observable.OnSubscribe() { + Observable openings = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 50); @@ -70,7 +70,7 @@ public void call(Subscriber observer) { Func1> closer = new Func1>() { @Override public Observable call(Object opening) { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, new Object(), 100); @@ -94,7 +94,7 @@ public void testObservableBasedCloser() { final List list = new ArrayList(); final List> lists = new ArrayList>(); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -110,7 +110,7 @@ public void call(Subscriber observer) { int calls; @Override public Observable call() { - return Observable.create(new Observable.OnSubscribe() { + return Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { int c = calls++; diff --git a/src/test/java/rx/internal/operators/OperatorWindowWithTimeTest.java b/src/test/java/rx/internal/operators/OperatorWindowWithTimeTest.java index 71a59ff510..78500db578 100644 --- a/src/test/java/rx/internal/operators/OperatorWindowWithTimeTest.java +++ b/src/test/java/rx/internal/operators/OperatorWindowWithTimeTest.java @@ -45,7 +45,7 @@ public void testTimedAndCount() { final List list = new ArrayList(); final List> lists = new ArrayList>(); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 10); @@ -78,7 +78,7 @@ public void testTimed() { final List list = new ArrayList(); final List> lists = new ArrayList>(); - Observable source = Observable.create(new Observable.OnSubscribe() { + Observable source = Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { push(observer, "one", 98); diff --git a/src/test/java/rx/internal/operators/OperatorZipTest.java b/src/test/java/rx/internal/operators/OperatorZipTest.java index 908f44a23d..5f4b06bcbe 100644 --- a/src/test/java/rx/internal/operators/OperatorZipTest.java +++ b/src/test/java/rx/internal/operators/OperatorZipTest.java @@ -94,7 +94,7 @@ public void testStartpingDifferentLengthObservableSequences1() { TestObservable w2 = new TestObservable(); TestObservable w3 = new TestObservable(); - Observable zipW = Observable.zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr()); + Observable zipW = Observable.zip(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2), Observable.unsafeCreate(w3), getConcat3StringsZipr()); zipW.subscribe(w); /* simulate sending data */ @@ -128,7 +128,7 @@ public void testStartpingDifferentLengthObservableSequences2() { TestObservable w2 = new TestObservable(); TestObservable w3 = new TestObservable(); - Observable zipW = Observable.zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr()); + Observable zipW = Observable.zip(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2), Observable.unsafeCreate(w3), getConcat3StringsZipr()); zipW.subscribe(w); /* simulate sending data */ @@ -1212,7 +1212,7 @@ public boolean hasNext() { Observable OBSERVABLE_OF_5_INTEGERS = OBSERVABLE_OF_5_INTEGERS(new AtomicInteger()); Observable OBSERVABLE_OF_5_INTEGERS(final AtomicInteger numEmitted) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber o) { @@ -1231,7 +1231,7 @@ public void call(final Subscriber o) { } Observable ASYNC_OBSERVABLE_OF_INFINITE_INTEGERS(final CountDownLatch latch) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber o) { diff --git a/src/test/java/rx/internal/operators/SafeSubscriberTest.java b/src/test/java/rx/internal/operators/SafeSubscriberTest.java index cf71a8f5d1..57fc3446bc 100644 --- a/src/test/java/rx/internal/operators/SafeSubscriberTest.java +++ b/src/test/java/rx/internal/operators/SafeSubscriberTest.java @@ -38,7 +38,7 @@ public class SafeSubscriberTest { @Test public void testOnNextAfterOnError() { TestObservable t = new TestObservable(); - Observable st = Observable.create(t); + Observable st = Observable.unsafeCreate(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @@ -60,7 +60,7 @@ public void testOnNextAfterOnError() { @Test public void testOnCompletedAfterOnError() { TestObservable t = new TestObservable(); - Observable st = Observable.create(t); + Observable st = Observable.unsafeCreate(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @@ -82,7 +82,7 @@ public void testOnCompletedAfterOnError() { @Test public void testOnNextAfterOnCompleted() { TestObservable t = new TestObservable(); - Observable st = Observable.create(t); + Observable st = Observable.unsafeCreate(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @@ -105,7 +105,7 @@ public void testOnNextAfterOnCompleted() { @Test public void testOnErrorAfterOnCompleted() { TestObservable t = new TestObservable(); - Observable st = Observable.create(t); + Observable st = Observable.unsafeCreate(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); diff --git a/src/test/java/rx/internal/operators/SingleOperatorCastTest.java b/src/test/java/rx/internal/operators/SingleOperatorCastTest.java new file mode 100644 index 0000000000..6d60c8b411 --- /dev/null +++ b/src/test/java/rx/internal/operators/SingleOperatorCastTest.java @@ -0,0 +1,51 @@ +/** + * Copyright 2014 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.internal.operators; + +import org.junit.Test; +import rx.Observer; +import rx.Single; + +import static org.mockito.Mockito.*; + +public class SingleOperatorCastTest { + + @Test + public void testSingleCast() { + Single source = Single.just(1); + Single single = source.cast(Integer.class); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + single.subscribe(observer); + verify(observer, times(1)).onNext(1); + verify(observer, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(observer, times(1)).onCompleted(); + } + + @Test + public void testSingleCastWithWrongType() { + Single source = Single.just(1); + Single single = source.cast(Boolean.class); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + single.subscribe(observer); + verify(observer, times(1)).onError( + org.mockito.Matchers.any(ClassCastException.class)); + } +} diff --git a/src/test/java/rx/internal/producers/ProducersTest.java b/src/test/java/rx/internal/producers/ProducersTest.java index f41fcda71a..46533b8ada 100644 --- a/src/test/java/rx/internal/producers/ProducersTest.java +++ b/src/test/java/rx/internal/producers/ProducersTest.java @@ -365,7 +365,7 @@ public void testObserverArbiterAsync() { .map(plus(40)) ); - Observable source = Observable.create( + Observable source = Observable.unsafeCreate( new SwitchTimer(timers, 550, TimeUnit.MILLISECONDS, test)); diff --git a/src/test/java/rx/observables/AsyncOnSubscribeTest.java b/src/test/java/rx/observables/AsyncOnSubscribeTest.java index a711d92c91..72afaad285 100644 --- a/src/test/java/rx/observables/AsyncOnSubscribeTest.java +++ b/src/test/java/rx/observables/AsyncOnSubscribeTest.java @@ -110,7 +110,7 @@ public void testSubscribedByBufferingOperator() { public void call(Long requested, Observer> observer) { observer.onNext(Observable.range(1, requested.intValue())); }}); - Observable.create(os).observeOn(scheduler).subscribe(subscriber); + Observable.unsafeCreate(os).observeOn(scheduler).subscribe(subscriber); subscriber.requestMore(RxRingBuffer.SIZE); scheduler.advanceTimeBy(10, TimeUnit.DAYS); subscriber.assertNoErrors(); @@ -141,7 +141,7 @@ public Integer call(Integer state, Long requested, Observer> observer) { observer.onCompleted(); }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertNoErrors(); subscriber.assertCompleted(); @@ -174,7 +174,7 @@ public void call(Long requested, Observer> observe observer.onNext(Observable.just(2)); } }); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertError(IllegalStateException.class); subscriber.assertNotCompleted(); @@ -189,7 +189,7 @@ public void testThrowException() throws InterruptedException { public void call(Long requested, Observer> observer) { throw new TestException(); }}); - Observable.create(os).subscribe(subscriber); + Observable.unsafeCreate(os).subscribe(subscriber); subscriber.requestMore(1); subscriber.assertError(TestException.class); subscriber.assertNotCompleted(); @@ -209,7 +209,7 @@ public Integer call(Integer state, Long requested, Observer os = Observable.create(AsyncOnSubscribe. createStateful( + new Func0() { + + @Override + public Integer call() { + return 0; + } + + }, + new Func3>, Integer>() { + + @Override + public Integer call(Integer state, Long requested, Observer> emitter) { + if (state == 0) { + emitter.onNext(Observable.range(0,100).delay(1, TimeUnit.SECONDS, scheduler)); + } else { + emitter.onCompleted(); + } + return state + 1; + } + + })); + + TestSubscriber ts = new TestSubscriber(); + os.mergeWith(Observable.just(0)).subscribe(ts); + scheduler.advanceTimeBy(1, TimeUnit.HOURS); + ts.assertCompleted(); + ts.assertValueCount(101); + } + } \ No newline at end of file diff --git a/src/test/java/rx/observables/BlockingObservableTest.java b/src/test/java/rx/observables/BlockingObservableTest.java index dfb57d26ff..fc802f978a 100644 --- a/src/test/java/rx/observables/BlockingObservableTest.java +++ b/src/test/java/rx/observables/BlockingObservableTest.java @@ -259,7 +259,7 @@ public void testToIterableManyTimes() { @Test(expected = TestException.class) public void testToIterableWithException() { - BlockingObservable obs = BlockingObservable.from(Observable.create(new Observable.OnSubscribe() { + BlockingObservable obs = BlockingObservable.from(Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(Subscriber observer) { @@ -281,7 +281,7 @@ public void call(Subscriber observer) { @Test public void testForEachWithError() { try { - BlockingObservable.from(Observable.create(new Observable.OnSubscribe() { + BlockingObservable.from(Observable.unsafeCreate(new Observable.OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -382,7 +382,7 @@ public Boolean call(String args) { @Test public void testSingleOrDefaultUnsubscribe() throws InterruptedException { final CountDownLatch unsubscribe = new CountDownLatch(1); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber subscriber) { subscriber.add(Subscriptions.create(new Action0() { diff --git a/src/test/java/rx/observables/SyncOnSubscribeTest.java b/src/test/java/rx/observables/SyncOnSubscribeTest.java index 630561b0a3..a219e1d7b7 100644 --- a/src/test/java/rx/observables/SyncOnSubscribeTest.java +++ b/src/test/java/rx/observables/SyncOnSubscribeTest.java @@ -52,7 +52,7 @@ public void call(Observer subscriber) { TestSubscriber ts = new TestSubscriber(); - Observable.create(os).subscribe(ts); + Observable.unsafeCreate(os).subscribe(ts); ts.assertNoErrors(); ts.assertTerminalEvent(); @@ -82,7 +82,7 @@ public void call(Integer t) { TestSubscriber ts = new TestSubscriber(); - Observable.create(os).subscribe(ts); + Observable.unsafeCreate(os).subscribe(ts); ts.assertNoErrors(); ts.assertTerminalEvent(); @@ -103,7 +103,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, times(1)).onNext(1); verify(o, never()).onNext(2); @@ -124,7 +124,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, times(1)).onNext(1); verify(o, times(1)).onCompleted(); @@ -144,7 +144,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, times(1)).onNext(1); verify(o, times(1)).onCompleted(); @@ -171,7 +171,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, times(1)).onNext(1); verify(o, never()).onCompleted(); @@ -190,7 +190,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onNext(any(Integer.class)); verify(o, never()).onError(any(Throwable.class)); @@ -206,7 +206,7 @@ public void call(Observer subscriber) { }}); - Observable neverObservable = Observable.create(os).subscribeOn(Schedulers.newThread()); + Observable neverObservable = Observable.unsafeCreate(os).subscribeOn(Schedulers.newThread()); Observable merged = Observable.amb(neverObservable, Observable.timer(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.newThread())); Iterator values = merged.toBlocking().toIterable().iterator(); @@ -225,7 +225,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onNext(any(Integer.class)); verify(o, never()).onCompleted(); @@ -243,7 +243,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onNext(any(Integer.class)); verify(o, times(1)).onCompleted(); @@ -268,7 +268,7 @@ public void onStart() { } }; - Observable.create(os).subscribe(ts); + Observable.unsafeCreate(os).subscribe(ts); ts.requestMore(1); @@ -288,7 +288,7 @@ public void call(Observer subscriber) { @SuppressWarnings("unchecked") Observer o = mock(Observer.class); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onNext(any(Integer.class)); verify(o).onError(any(TestException.class)); @@ -319,7 +319,7 @@ public Integer call(Integer state, Observer subscriber) { Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onError(any(TestException.class)); inOrder.verify(o, times(count)).onNext(any(Integer.class)); @@ -357,7 +357,7 @@ public Iterator call(Iterator it, Observer ob Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - Observable.create(os).subscribe(o); + Observable.unsafeCreate(os).subscribe(o); verify(o, never()).onError(any(TestException.class)); inOrder.verify(o, times(n)).onNext(any()); @@ -385,7 +385,7 @@ public Integer call(Integer state, Observer observer) { Observer o = mock(Observer.class); InOrder inOrder = inOrder(o); - Observable.create(os).take(finalCount).subscribe(o); + Observable.unsafeCreate(os).take(finalCount).subscribe(o); verify(o, never()).onError(any(Throwable.class)); inOrder.verify(o, times(finalCount)).onNext(any()); @@ -416,7 +416,7 @@ public Integer call(Integer state, Observer observer) { onUnSubscribe); TestSubscriber ts = new TestSubscriber(0); - Observable.create(os).subscribe(ts); + Observable.unsafeCreate(os).subscribe(ts); ts.requestMore(finalCount); @@ -451,7 +451,7 @@ public Integer call(Integer state, Observer observer) { TestSubscriber ts = new TestSubscriber(o); - Observable.create(os).take(1).subscribe(ts); + Observable.unsafeCreate(os).take(1).subscribe(ts); verify(o, never()).onError(any(Throwable.class)); verify(onUnSubscribe, times(1)).call(any(Integer.class)); @@ -521,7 +521,7 @@ public Integer call(Integer state, Observer observer) { InOrder inOrder = inOrder(o); final TestSubscriber ts = new TestSubscriber(o); - Observable.create(os).subscribeOn(Schedulers.newThread()).subscribe(ts); + Observable.unsafeCreate(os).subscribeOn(Schedulers.newThread()).subscribe(ts); // wait until the first request has started processing if (!l2.await(2, TimeUnit.SECONDS)) { @@ -569,7 +569,7 @@ public void call() { final CountDownLatch latch = new CountDownLatch(1); final TestSubscriber ts = new TestSubscriber(o); - Observable.create(os).lift(new Operator() { + Observable.unsafeCreate(os).lift(new Operator() { @Override public Subscriber call(final Subscriber subscriber) { return new Subscriber(subscriber) { @@ -634,7 +634,7 @@ public Map call(Map state, Observer source = Observable.create(os); + Observable source = Observable.unsafeCreate(os); for (int i = 0; i < count; i++) { source.subscribe(); } @@ -678,7 +678,7 @@ public void call(Integer t) { subs.add(ts); } TestScheduler scheduler = new TestScheduler(); - Observable o2 = Observable.create(os).subscribeOn(scheduler); + Observable o2 = Observable.unsafeCreate(os).subscribeOn(scheduler); for (Subscriber ts : subs) { o2.subscribe(ts); } @@ -719,7 +719,7 @@ public Integer call(Integer calls, Observer observer) { TestSubscriber ts = new TestSubscriber(); TestScheduler scheduler = new TestScheduler(); - Observable.create(os).observeOn(scheduler).subscribe(ts); + Observable.unsafeCreate(os).observeOn(scheduler).subscribe(ts); scheduler.triggerActions(); ts.awaitTerminalEvent(); @@ -748,7 +748,7 @@ public void call(Observer observer) { }}, onUnSubscribe); final AtomicReference exception = new AtomicReference(); - Observable.create(os).subscribe(new Subscriber() { + Observable.unsafeCreate(os).subscribe(new Subscriber() { @Override public void onCompleted() { diff --git a/src/test/java/rx/observers/AssertableSubscriberTest.java b/src/test/java/rx/observers/AssertableSubscriberTest.java index 838f57d6ec..4c6adbb06d 100644 --- a/src/test/java/rx/observers/AssertableSubscriberTest.java +++ b/src/test/java/rx/observers/AssertableSubscriberTest.java @@ -151,7 +151,7 @@ public void testSingle() { assertEquals(Thread.currentThread().getName(), ts.getLastSeenThread().getName()); assertTrue(ts.getOnErrorEvents().isEmpty()); assertTrue(ts.getOnNextEvents().isEmpty()); - assertEquals(1, ts.getValueCount()); + assertEquals(0, ts.getValueCount()); } @Test diff --git a/src/test/java/rx/observers/SerializedObserverTest.java b/src/test/java/rx/observers/SerializedObserverTest.java index b53ac9fd5d..83f49ecd52 100644 --- a/src/test/java/rx/observers/SerializedObserverTest.java +++ b/src/test/java/rx/observers/SerializedObserverTest.java @@ -50,7 +50,7 @@ private Observer serializedObserver(Observer o) { @Test public void testSingleThreadedBasic() { TestSingleThreadedObservable onSubscribe = new TestSingleThreadedObservable("one", "two", "three"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); Observer aw = serializedObserver(observer); @@ -70,7 +70,7 @@ public void testSingleThreadedBasic() { @Test public void testMultiThreadedBasic() { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyObserver = new BusyObserver(); Observer aw = serializedObserver(busyObserver); @@ -94,7 +94,7 @@ public void testMultiThreadedBasic() { @Test(timeout = 1000) public void testMultiThreadedWithNPE() throws InterruptedException { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three", null); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyObserver = new BusyObserver(); Observer aw = serializedObserver(busyObserver); @@ -128,7 +128,7 @@ public void testMultiThreadedWithNPEinMiddle() { for (int i = 0; i < n; i++) { TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable("one", "two", "three", null, "four", "five", "six", "seven", "eight", "nine"); - Observable w = Observable.create(onSubscribe); + Observable w = Observable.unsafeCreate(onSubscribe); BusyObserver busyObserver = new BusyObserver(); Observer aw = serializedObserver(busyObserver); @@ -391,7 +391,7 @@ private static void waitOnThreads(Future... futures) { } private static Observable infinite(final AtomicInteger produced) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber s) { diff --git a/src/test/java/rx/observers/TestSubscriberTest.java b/src/test/java/rx/observers/TestSubscriberTest.java index ee94f12297..a78890aa74 100644 --- a/src/test/java/rx/observers/TestSubscriberTest.java +++ b/src/test/java/rx/observers/TestSubscriberTest.java @@ -72,7 +72,7 @@ public void testAssertNotMatchValue() { oi.subscribe(o); thrown.expect(AssertionError.class); - thrown.expectMessage("Value at index: 1 expected to be [3] (Integer) but was: [2] (Integer)"); + thrown.expectMessage("Value at index: 1 expected: [3] (Integer) but was: [2] (Integer)"); o.assertReceivedOnNext(Arrays.asList(1, 3)); @@ -815,4 +815,15 @@ public void assertAndConsume() { ts.assertNoValues(); } + + @Test + public void assertAndClearResetsValueCount() { + TestSubscriber ts = TestSubscriber.create(); + + ts.onNext(1); + ts.assertValuesAndClear(1); + + ts.assertNoValues(); + Assert.assertEquals(0, ts.getValueCount()); + } } diff --git a/src/test/java/rx/plugins/RxJavaPluginsTest.java b/src/test/java/rx/plugins/RxJavaPluginsTest.java index 1c69ff1389..50d6944a08 100644 --- a/src/test/java/rx/plugins/RxJavaPluginsTest.java +++ b/src/test/java/rx/plugins/RxJavaPluginsTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.mock; +import java.security.Permission; import java.util.*; import java.util.concurrent.TimeUnit; @@ -344,4 +345,75 @@ public void onNext(Object o) { assertEquals(re, errorHandler.e); assertEquals(1, errorHandler.count); } + + @Test + public void systemPropertiesSecurityException() { + assertNull(RxJavaPlugins.getPluginImplementationViaProperty(Object.class, new Properties() { + + private static final long serialVersionUID = -4291534158508255616L; + + @Override + public Set> entrySet() { + return new HashSet>() { + + private static final long serialVersionUID = -7714005655772619143L; + + @Override + public Iterator> iterator() { + return new Iterator>() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Map.Entry next() { + throw new SecurityException(); + }; + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + @Override + public synchronized Object clone() { + return this; + } + })); + } + + @Test + public void securityManagerDenySystemProperties() { + SecurityManager old = System.getSecurityManager(); + try { + SecurityManager sm = new SecurityManager() { + @Override + public void checkPropertiesAccess() { + throw new SecurityException(); + } + + @Override + public void checkPermission(Permission perm) { + // allow restoring the security manager + } + + @Override + public void checkPermission(Permission perm, Object context) { + // allow restoring the security manager + } + }; + + System.setSecurityManager(sm); + assertTrue(RxJavaPlugins.getSystemPropertiesSafe().isEmpty()); + } finally { + System.setSecurityManager(old); + } + + assertFalse(RxJavaPlugins.getSystemPropertiesSafe().isEmpty()); + } } diff --git a/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java b/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java index a7cec8ab86..1a9541997c 100644 --- a/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java +++ b/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java @@ -297,7 +297,7 @@ public void testRecursionAndOuterUnsubscribe() throws InterruptedException { final CountDownLatch completionLatch = new CountDownLatch(1); final Worker inner = getScheduler().createWorker(); try { - Observable obs = Observable.create(new OnSubscribe() { + Observable obs = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { inner.schedule(new Action0() { diff --git a/src/test/java/rx/schedulers/AbstractSchedulerTests.java b/src/test/java/rx/schedulers/AbstractSchedulerTests.java index 5875328d70..ff5c94071a 100644 --- a/src/test/java/rx/schedulers/AbstractSchedulerTests.java +++ b/src/test/java/rx/schedulers/AbstractSchedulerTests.java @@ -322,7 +322,7 @@ public void call() { @Test public final void testRecursiveSchedulerInObservable() { - Observable obs = Observable.create(new OnSubscribe() { + Observable obs = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { final Scheduler.Worker inner = getScheduler().createWorker(); @@ -362,7 +362,7 @@ public void call(Integer v) { public final void testConcurrentOnNextFailsValidation() throws InterruptedException { final int count = 10; final CountDownLatch latch = new CountDownLatch(count); - Observable o = Observable.create(new OnSubscribe() { + Observable o = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber observer) { @@ -423,7 +423,7 @@ public final void testSubscribeOnNestedConcurrency() throws InterruptedException @Override public Observable call(final String v) { - return Observable.create(new OnSubscribe() { + return Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber observer) { diff --git a/src/test/java/rx/schedulers/TestSchedulerTest.java b/src/test/java/rx/schedulers/TestSchedulerTest.java index 18e3e46913..f26939d5bf 100644 --- a/src/test/java/rx/schedulers/TestSchedulerTest.java +++ b/src/test/java/rx/schedulers/TestSchedulerTest.java @@ -181,7 +181,7 @@ public final void testNestedSchedule() { final Action0 calledOp = mock(Action0.class); Observable poller; - poller = Observable.create(new OnSubscribe() { + poller = Observable.unsafeCreate(new OnSubscribe() { @Override public void call(final Subscriber aSubscriber) { inner.schedule(new Action0() { @@ -215,7 +215,7 @@ public void call() { inner.unsubscribe(); } } - + @Test public void resolution() { for (final TimeUnit unit : TimeUnit.values()) { diff --git a/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java b/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java index 72149ee677..954a9b8950 100644 --- a/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java +++ b/src/test/java/rx/subjects/ReplaySubjectBoundedConcurrencyTest.java @@ -52,7 +52,7 @@ public void testReplaySubjectConcurrentSubscribersDoingReplayDontBlockEachOther( @Override public void run() { - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { diff --git a/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java b/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java index 5e1c350462..69b4c8cf69 100644 --- a/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java +++ b/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java @@ -52,7 +52,7 @@ public void testReplaySubjectConcurrentSubscribersDoingReplayDontBlockEachOther( @Override public void run() { - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { @@ -174,7 +174,7 @@ public static void concurrencyTest(final ReplaySubject replay) throws Inte @Override public void run() { - Observable.create(new OnSubscribe() { + Observable.unsafeCreate(new OnSubscribe() { @Override public void call(Subscriber o) { diff --git a/src/test/java/rx/subjects/ReplaySubjectTest.java b/src/test/java/rx/subjects/ReplaySubjectTest.java index 8464cf1b3f..63b63ea16c 100644 --- a/src/test/java/rx/subjects/ReplaySubjectTest.java +++ b/src/test/java/rx/subjects/ReplaySubjectTest.java @@ -1175,4 +1175,22 @@ public Boolean call(Integer v) { ts2.assertValues(1, 2, 3, 6, 7); } + @Test + public void noOldEntries() { + TestScheduler scheduler = new TestScheduler(); + + ReplaySubject source = ReplaySubject.createWithTime(2, TimeUnit.SECONDS, scheduler); + + source.onNext(1); + source.onCompleted(); + + source.test().assertResult(1); + + source.test().assertResult(1); + + scheduler.advanceTimeBy(3, TimeUnit.SECONDS); + + source.test().assertResult(); + } + } diff --git a/src/test/java/rx/subjects/UnicastSubjectTest.java b/src/test/java/rx/subjects/UnicastSubjectTest.java new file mode 100644 index 0000000000..af9ef33468 --- /dev/null +++ b/src/test/java/rx/subjects/UnicastSubjectTest.java @@ -0,0 +1,101 @@ +package rx.subjects; + +import org.junit.Test; +import rx.functions.Action0; +import rx.observers.TestSubscriber; + +public class UnicastSubjectTest { + + @Test + public void testOneArgFactoryDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(true); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(2); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testOneArgFactoryNoDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(false); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testThreeArgsFactoryDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16, new NoopAction0(), true); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(2); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testThreeArgsFactoryNoDelayError() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16, new NoopAction0(), false); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testZeroArgsFactory() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testOneArgFactory() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + @Test + public void testTwoArgsFactory() throws Exception { + TestSubscriber subscriber = TestSubscriber.create(); + UnicastSubject s = UnicastSubject.create(16, new NoopAction0()); + s.onNext(1L); + s.onNext(2L); + s.onError(new RuntimeException()); + s.subscribe(subscriber); + subscriber.assertValueCount(0); + subscriber.assertError(RuntimeException.class); + } + + + + private static final class NoopAction0 implements Action0 { + + @Override + public void call() { + } + } +}