diff --git a/.travis.yml b/.travis.yml index 83835caeba..9c5c0f6909 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: java jdk: -- oraclejdk8 +- openjdk8 # force upgrade Java8 as per https://github.com/travis-ci/travis-ci/issues/4042 (fixes compilation issue) #addons: diff --git a/CHANGES.md b/CHANGES.md index 16757d46b4..8842673756 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,174 @@ The changelog of version 1.x can be found at https://github.com/ReactiveX/RxJava/blob/1.x/CHANGES.md +### Version 2.2.21 - February 10, 2021 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.21%7C)) +[JavaDocs](http://reactivex.io/RxJava/2.x/javadoc/2.2.21) + +:warning: This is the last planned update for the 2.x version line. After **February 28, 2021**, 2.x becomes **End-of-Life** (EoL); no further patches, bugfixes, enhancements, documentation or support will be provided by the project. + + +#### Enhancements + +- Add a system parameter to allow scheduled worker release in the Io `Scheduler`. (#7162) +- Add a system parameter to allow `Scheduler`s to use `System.nanoTime()` for `now()`. (#7170) + +### Version 2.2.20 - October 6, 2020 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.20%7C)) +[JavaDocs](http://reactivex.io/RxJava/2.x/javadoc/2.2.20) + +:warning: The 2.x version line is now in **maintenance mode** and will be supported only through bugfixes until **February 28, 2021**. No new features, behavior changes or documentation adjustments will be accepted or applied to 2.x. It is recommended to migrate to [3.x](https://github.com/ReactiveX/RxJava/tree/3.x) within this time period. + +#### Bugfixes + +- Fix `Observable.flatMap` with `maxConcurrency` hangs (#6960) +- Fix `Observable.toFlowable(ERROR)` not cancelling upon `MissingBackpressureException` (#7084) +- Fix `Flowable.concatMap` backpressure with scalars. (#7091) + +### Version 2.2.19 - March 14, 2020 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.19%7C)) +[JavaDocs](http://reactivex.io/RxJava/2.x/javadoc/2.2.19) + +:warning: The 2.x version line is now in **maintenance mode** and will be supported only through bugfixes until **February 28, 2021**. No new features, behavior changes or documentation adjustments will be accepted or applied to 2.x. It is recommended to migrate to [3.x](https://github.com/ReactiveX/RxJava/tree/3.x) within this time period. + +#### Bugfixes + + - [Commit 7980c85b](https://github.com/ReactiveX/RxJava/commit/7980c85b18dd46ec2cd2cf49477363f1268d3a98): Fix `switchMap` not canceling properly during `onNext`-`cancel` races. + + +### Version 2.2.18 - February 21, 2020 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.18%7C)) + +:warning: The 2.x version line is now in **maintenance mode** and will be supported only through bugfixes until **February 28, 2021**. No new features, behavior changes or documentation adjustments will be accepted or applied to 2.x. It is recommended to migrate to [3.x](https://github.com/ReactiveX/RxJava/tree/3.x) within this time period. + +#### Bugfixes + + - [Pull 6894](https://github.com/ReactiveX/RxJava/pull/6894): Fix `groupBy` not requesting more if a group is cancelled with buffered items. + +### Version 2.2.17 - January 12, 2020 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.17%7C)) + +#### Bugfixes + + - [Pull 6827](https://github.com/ReactiveX/RxJava/pull/6827): Fix `Flowable.flatMap` not canceling the inner sources on outer error. + +### Version 2.2.16 - December 15, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.16%7C)) + +#### Bugfixes + + - [Pull 6754](https://github.com/ReactiveX/RxJava/pull/6754): Fix `amb`, `combineLatest` and `zip` `Iterable` overloads throwing `ArrayStoreException` for `ObservableSource`s. + +#### Documentation changes + + - [Pull 6746](https://github.com/ReactiveX/RxJava/pull/6746): Fix self-see references, some comments. + +### Version 2.2.15 - November 24, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.15%7C)) + +#### Bugfixes + + - [Pull 6715](https://github.com/ReactiveX/RxJava/pull/6715): Fix `MulticastProcessor` not requesting more after limit is reached. + - [Pull 6710](https://github.com/ReactiveX/RxJava/pull/6710): Fix concurrent `clear` in `observeOn` while output-fused. + - [Pull 6720](https://github.com/ReactiveX/RxJava/pull/6720): Fix `parallel()` on grouped flowable not replenishing properly. + +#### Documentation changes + + - [Pull 6722](https://github.com/ReactiveX/RxJava/pull/6722): Update javadoc for `observeOn` to mention its eagerness. + +#### Other changes + + - [Pull 6704](https://github.com/ReactiveX/RxJava/pull/6704): Add ProGuard rule to avoid `j.u.c.Flow` warnings due to RS 1.0.3. + +### Version 2.2.14 - November 2, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.14%7C)) + +#### Bugfixes + + - [Pull 6677](https://github.com/ReactiveX/RxJava/pull/6677): Fix concurrent `clear()` calls when fused chains are canceled. + - [Pull 6684](https://github.com/ReactiveX/RxJava/pull/6684): Fix `window(time)` possible interrupts while terminating. + +#### Documentation changes + + - [Pull 6681](https://github.com/ReactiveX/RxJava/pull/6681): Backport marble diagrams for `Single` from 3.x. + +### Version 2.2.13 - October 3, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.13%7C)) + +#### Dependencies + + - [Commit cc690ff2](https://github.com/ReactiveX/RxJava/commit/cc690ff2f757873b11cd075ebc22262f76f28459): Upgrade to **Reactive Streams 1.0.3**. + +#### Bugfixes + + - [Commit cc690ff2](https://github.com/ReactiveX/RxJava/commit/cc690ff2f757873b11cd075ebc22262f76f28459): Avoid using `System.getProperties()`. + - [Pull 6653](https://github.com/ReactiveX/RxJava/pull/6653): Fix `takeLast(time)` last events time window calculation. + - [Pull 6657](https://github.com/ReactiveX/RxJava/pull/6657): Fix size+time bound `window` not creating windows properly. + +### Version 2.2.12 - August 25, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.12%7C)) + +#### Bugfixes + + - [Pull 6618](https://github.com/ReactiveX/RxJava/pull/6618): Fix `switchMap` incorrect sync-fusion & error management. + - [Pull 6627](https://github.com/ReactiveX/RxJava/pull/6627): Fix `blockingIterable` hang when force-disposed. + - [Pull 6629](https://github.com/ReactiveX/RxJava/pull/6629): Fix `refCount` not resetting when cross-canceled. + +### Version 2.2.11 - August 2, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.11%7C)) + +#### Bugfixes + + - [Pull 6560](https://github.com/ReactiveX/RxJava/pull/6560): Fix NPE when debouncing an empty source. + - [Pull 6599](https://github.com/ReactiveX/RxJava/pull/6599): Fix `mergeWith` not canceling other when the main fails. + - [Pull 6601](https://github.com/ReactiveX/RxJava/pull/6601): `ObservableBlockingSubscribe` compares with wrong object. + - [Pull 6602](https://github.com/ReactiveX/RxJava/pull/): Fix truncation bugs in `replay()` and `ReplaySubject`/`Processor`. + +#### Documentation changes + + - [Pull 6565](https://github.com/ReactiveX/RxJava/pull/6565): Fix JavaDocs of `Single.doOnTerminate` refer to `onComplete` notification. + +### Version 2.2.10 - June 21, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.10%7C)) + +#### Bugfixes + + - [Pull 6499](https://github.com/ReactiveX/RxJava/pull/6499): Add missing null check to `BufferExactBoundedObserver`. + - [Pull 6505](https://github.com/ReactiveX/RxJava/pull/6505): Fix `publish().refCount()` hang due to race. + - [Pull 6522](https://github.com/ReactiveX/RxJava/pull/6522): Fix `concatMapDelayError` not continuing on fused inner source crash. + +#### Documentation changes + + - [Pull 6496](https://github.com/ReactiveX/RxJava/pull/6496): Fix outdated links in `Additional-Reading.md`. + - [Pull 6497](https://github.com/ReactiveX/RxJava/pull/6497): Fix links in `Alphabetical-List-of-Observable-Operators.md`. + - [Pull 6504](https://github.com/ReactiveX/RxJava/pull/6504): Fix various Javadocs & imports. + - [Pull 6506](https://github.com/ReactiveX/RxJava/pull/6506): Expand the Javadoc of `Flowable`. + - [Pull 6510](https://github.com/ReactiveX/RxJava/pull/6510): Correct "Reactive-Streams" to "Reactive Streams" in documentation. + +### Version 2.2.9 - May 30, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.9%7C)) + +#### Bugfixes + + - [Pull 6488](https://github.com/ReactiveX/RxJava/pull/6488): Fix `zip` not stopping the subscription upon eager error. + +#### Documentation changes + + - [Pull 6453](https://github.com/ReactiveX/RxJava/pull/6453): Fixed wrong type referenced in `Maybe` and `Single` JavaDocs. + - [Pull 6458](https://github.com/ReactiveX/RxJava/pull/6458): Update the Javadoc of the `retry` operator. + +#### Other + + - [Pull 6452](https://github.com/ReactiveX/RxJava/pull/6452): Remove dependency of `Schedulers` from `ObservableRefCount`. + - [Pull 6461](https://github.com/ReactiveX/RxJava/pull/6461): Change error message in `ObservableFromArray`. + - [Pull 6469](https://github.com/ReactiveX/RxJava/pull/6469): Remove redundant methods from `sample(Observable)`. + - [Pull 6470](https://github.com/ReactiveX/RxJava/pull/6470): Remove unused import from `Flowable.java`. + - [Pull 6485](https://github.com/ReactiveX/RxJava/pull/6485): Remove unused `else` from the `Observable`. + +### Version 2.2.8 - March 26, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.8%7C)) + +#### Bugfixes + + - [Pull 6442](https://github.com/ReactiveX/RxJava/pull/6442): Add missing undeliverable error handling logic for `Completable.fromRunnable` & `fromAction` operators. + +#### Documentation changes + + - [Pull 6432](https://github.com/ReactiveX/RxJava/pull/6432): Improve the docs of `CompositeDisposable`. + - [Pull 6434](https://github.com/ReactiveX/RxJava/pull/6434): Improve subjects and processors package doc. + - [Pull 6436](https://github.com/ReactiveX/RxJava/pull/6436): Improve `Creating-Observables` wiki doc. + + +#### Other + + - [Pull 6433](https://github.com/ReactiveX/RxJava/pull/6433): Make error messages of parameter checks consistent. + ### Version 2.2.7 - February 23, 2019 ([Maven](http://search.maven.org/#artifactdetails%7Cio.reactivex.rxjava2%7Crxjava%7C2.2.7%7C)) #### API enhancements diff --git a/DESIGN.md b/DESIGN.md index 53f828186a..5480b39948 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -528,13 +528,13 @@ interface ScalarCallable extends java.util.Callable { `ScalarCallable` is also `Callable` and thus its value can be extracted practically anytime. For convenience (and for sense), `ScalarCallable` overrides and hides the superclass' `throws Exception` clause - throwing during assembly time is likely unreasonable for scalars. -Since Reactive-Streams doesn't allow `null`s in the value flow, we have the opportunity to define `ScalarCallable`s and `Callable`s returning `null` should be considered as an empty source - allowing operators to dispatch on the type `Callable` first then branch on the nullness of `call()`. +Since Reactive Streams doesn't allow `null`s in the value flow, we have the opportunity to define `ScalarCallable`s and `Callable`s returning `null` should be considered as an empty source - allowing operators to dispatch on the type `Callable` first then branch on the nullness of `call()`. Interoperating with other libraries, at this level is possible. Reactor-Core uses the same pattern and the two libraries can work with each other's `Publisher+Callable` types. Unfortunately, this means subscription-time only fusion as `ScalarCallable`s live locally in each library. ##### Micro-fusion -Micro-fusion goes a step deeper and tries to reuse internal structures, mostly queues, in operator pairs, saving on allocation and sometimes on atomic operations. It's property is that, in a way, subverts the standard Reactive-Streams protocol between subsequent operators that both support fusion. However, from the outside world's view, they still work according to the RS protocol. +Micro-fusion goes a step deeper and tries to reuse internal structures, mostly queues, in operator pairs, saving on allocation and sometimes on atomic operations. It's property is that, in a way, subverts the standard Reactive Streams protocol between subsequent operators that both support fusion. However, from the outside world's view, they still work according to the RS protocol. Currently, two main kinds of micro-fusion opportunities are available. diff --git a/README.md b/README.md index c29d6d3fc0..1cd82b7f63 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,13 @@ # RxJava: Reactive Extensions for the JVM +## End-of-Life notice + +As of February 28, 2021, The RxJava 2.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 2.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=2.x)](https://codecov.io/gh/ReactiveX/RxJava/branch/2.x) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.reactivex.rxjava2/rxjava/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.reactivex.rxjava2/rxjava) @@ -10,7 +18,7 @@ It extends the [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) #### Version 2.x ([Javadoc](http://reactivex.io/RxJava/2.x/javadoc/)) -- single dependency: [Reactive-Streams](https://github.com/reactive-streams/reactive-streams-jvm) +- single dependency: [Reactive Streams](https://github.com/reactive-streams/reactive-streams-jvm) - continued support for Java 6+ & [Android](https://github.com/ReactiveX/RxAndroid) 2.3+ - performance gains through design changes learned through the 1.x cycle and through [Reactive-Streams-Commons](https://github.com/reactor/reactive-streams-commons) research project. - Java 8 lambda-friendly API @@ -72,7 +80,7 @@ Flowable.just("Hello world") RxJava 2 features several base classes you can discover operators on: - - [`io.reactivex.Flowable`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html): 0..N flows, supporting Reactive-Streams and backpressure + - [`io.reactivex.Flowable`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html): 0..N flows, supporting Reactive Streams and backpressure - [`io.reactivex.Observable`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Observable.html): 0..N flows, no backpressure, - [`io.reactivex.Single`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Single.html): a flow of exactly 1 item or an error, - [`io.reactivex.Completable`](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Completable.html): a flow without items but only a completion or error signal, @@ -258,12 +266,10 @@ Flowable.range(1, 10) ```java Flowable inventorySource = warehouse.getInventoryAsync(); -inventorySource.flatMap(inventoryItem -> - erp.getDemandAsync(inventoryItem.getId()) - .map(demand - -> System.out.println("Item " + inventoryItem.getName() + " has demand " + demand)); - ) - .subscribe(); +inventorySource + .flatMap(inventoryItem -> erp.getDemandAsync(inventoryItem.getId()) + .map(demand -> "Item " + inventoryItem.getName() + " has demand " + demand)) + .subscribe(System.out::println); ``` ### Continuations diff --git a/build.gradle b/build.gradle index 2f33e66093..48565d07d0 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ targetCompatibility = JavaVersion.VERSION_1_6 // --------------------------------------- def junitVersion = "4.12" -def reactiveStreamsVersion = "1.0.2" +def reactiveStreamsVersion = "1.0.3" def mockitoVersion = "2.1.0" def jmhLibVersion = "1.20" def testNgVersion = "6.11" diff --git a/docs/Additional-Reading.md b/docs/Additional-Reading.md index b5a4a2604a..d634e956a6 100644 --- a/docs/Additional-Reading.md +++ b/docs/Additional-Reading.md @@ -1,4 +1,4 @@ -(A more complete and up-to-date list of resources can be found at the reactivex.io site: [[http://reactivex.io/tutorials.html]]) +A more complete and up-to-date list of resources can be found at the [reactivex.io site](http://reactivex.io/tutorials.html) # Introducing Reactive Programming * [Introduction to Rx](http://www.introtorx.com/): a free, on-line book by Lee Campbell **(1.x)** @@ -10,7 +10,7 @@ * [Your Mouse is a Database](http://queue.acm.org/detail.cfm?id=2169076) by Erik Meijer * [A Playful Introduction to Rx](https://www.youtube.com/watch?v=WKore-AkisY) a video lecture by Erik Meijer * Wikipedia: [Reactive Programming](http://en.wikipedia.org/wiki/Reactive_programming) and [Functional Reactive Programming](http://en.wikipedia.org/wiki/Functional_reactive_programming) -* [What is Reactive Programming?](http://blog.hackhands.com/overview-of-reactive-programming/) a video presentation by Jafar Husain. +* [What is Reactive Programming?](https://www.youtube.com/watch?v=-8Y1-lE6NSA) a video presentation by Jafar Husain. * [2 minute introduction to Rx](https://medium.com/@andrestaltz/2-minute-introduction-to-rx-24c8ca793877) by André Staltz * StackOverflow: [What is (functional) reactive programming?](http://stackoverflow.com/a/1030631/1946802) * [The Reactive Manifesto](http://www.reactivemanifesto.org/) diff --git a/docs/Alphabetical-List-of-Observable-Operators.md b/docs/Alphabetical-List-of-Observable-Operators.md index 86495638c0..e5728356bc 100644 --- a/docs/Alphabetical-List-of-Observable-Operators.md +++ b/docs/Alphabetical-List-of-Observable-Operators.md @@ -1,250 +1,250 @@ -* **`aggregate( )`** — _see [**`reduce( )`**](Mathematical-and-Aggregate-Operators#reduce)_ -* [**`all( )`**](Conditional-and-Boolean-Operators#all) — determine whether all items emitted by an Observable meet some criteria -* [**`amb( )`**](Conditional-and-Boolean-Operators#amb) — given two or more source Observables, emits all of the items from the first of these Observables to emit an item -* **`ambWith( )`** — _instance version of [**`amb( )`**](Conditional-and-Boolean-Operators#amb)_ -* [**`and( )`**](Combining-Observables#and-then-and-when) — combine the emissions from two or more source Observables into a `Pattern` (`rxjava-joins`) -* **`apply( )`** (scala) — _see [**`create( )`**](Creating-Observables#create)_ -* **`asObservable( )`** (kotlin) — _see [**`from( )`**](Creating-Observables#from) (et al.)_ -* [**`asyncAction( )`**](Async-Operators#toasync-or-asyncaction-or-asyncfunc) — convert an Action into an Observable that executes the Action and emits its return value (`rxjava-async`) -* [**`asyncFunc( )`**](Async-Operators#toasync-or-asyncaction-or-asyncfunc) — convert a function into an Observable that executes the function and emits its return value (`rxjava-async`) -* [**`averageDouble( )`**](Mathematical-and-Aggregate-Operators#averageinteger-averagelong-averagefloat-and-averagedouble) — calculates the average of Doubles emitted by an Observable and emits this average (`rxjava-math`) -* [**`averageFloat( )`**](Mathematical-and-Aggregate-Operators#averageinteger-averagelong-averagefloat-and-averagedouble) — calculates the average of Floats emitted by an Observable and emits this average (`rxjava-math`) -* [**`averageInteger( )`**](Mathematical-and-Aggregate-Operators#averageinteger-averagelong-averagefloat-and-averagedouble) — calculates the average of Integers emitted by an Observable and emits this average (`rxjava-math`) -* [**`averageLong( )`**](Mathematical-and-Aggregate-Operators#averageinteger-averagelong-averagefloat-and-averagedouble) — calculates the average of Longs emitted by an Observable and emits this average (`rxjava-math`) -* **`blocking( )`** (clojure) — _see [**`toBlocking( )`**](Blocking-Observable-Operators)_ -* [**`buffer( )`**](Transforming-Observables#buffer) — periodically gather items from an Observable into bundles and emit these bundles rather than emitting the items one at a time -* [**`byLine( )`**](String-Observables#byline) (`StringObservable`) — converts an Observable of Strings into an Observable of Lines by treating the source sequence as a stream and splitting it on line-endings -* [**`cache( )`**](Observable-Utility-Operators#cache) — remember the sequence of items emitted by the Observable and emit the same sequence to future Subscribers -* [**`cast( )`**](Transforming-Observables#cast) — cast all items from the source Observable into a particular type before reemitting them -* **`catch( )`** (clojure) — _see [**`onErrorResumeNext( )`**](Error-Handling-Operators#onerrorresumenext)_ -* [**`chunkify( )`**](Phantom-Operators#chunkify) — returns an iterable that periodically returns a list of items emitted by the source Observable since the last list (??) -* [**`collect( )`**](Mathematical-and-Aggregate-Operators#collect) — collects items emitted by the source Observable into a single mutable data structure and returns an Observable that emits this structure -* [**`combineLatest( )`**](Combining-Observables#combinelatest) — when an item is emitted by either of two Observables, combine the latest item emitted by each Observable via a specified function and emit items based on the results of this function -* **`combineLatestWith( )`** (scala) — _instance version of [**`combineLatest( )`**](Combining-Observables#combinelatest)_ -* [**`concat( )`**](Mathematical-and-Aggregate-Operators#concat) — concatenate two or more Observables sequentially -* [**`concatMap( )`**](Transforming-Observables#flatmap-concatmap-and-flatmapiterable) — transform the items emitted by an Observable into Observables, then flatten this into a single Observable, without interleaving -* **`concatWith( )`** — _instance version of [**`concat( )`**](Mathematical-and-Aggregate-Operators#concat)_ -* [**`connect( )`**](Connectable-Observable-Operators#connectableobservableconnect) — instructs a Connectable Observable to begin emitting items -* **`cons( )`** (clojure) — _see [**`concat( )`**](Mathematical-and-Aggregate-Operators#concat)_ -* [**`contains( )`**](Conditional-and-Boolean-Operators#contains) — determine whether an Observable emits a particular item or not -* [**`count( )`**](Mathematical-and-Aggregate-Operators#count-and-countlong) — counts the number of items emitted by an Observable and emits this count -* [**`countLong( )`**](Mathematical-and-Aggregate-Operators#count-and-countlong) — counts the number of items emitted by an Observable and emits this count -* [**`create( )`**](Creating-Observables#create) — create an Observable from scratch by means of a function -* **`cycle( )`** (clojure) — _see [**`repeat( )`**](Creating-Observables#repeat)_ -* [**`debounce( )`**](Filtering-Observables#throttlewithtimeout-or-debounce) — only emit an item from the source Observable after a particular timespan has passed without the Observable emitting any other items -* [**`decode( )`**](String-Observables#decode) (`StringObservable`) — convert a stream of multibyte characters into an Observable that emits byte arrays that respect character boundaries -* [**`defaultIfEmpty( )`**](Conditional-and-Boolean-Operators#defaultifempty) — emit items from the source Observable, or emit a default item if the source Observable completes after emitting no items -* [**`defer( )`**](Creating-Observables#defer) — do not create the Observable until a Subscriber subscribes; create a fresh Observable on each subscription -* [**`deferFuture( )`**](Async-Operators#deferfuture) — convert a Future that returns an Observable into an Observable, but do not attempt to get the Observable that the Future returns until a Subscriber subscribes (`rxjava-async`) -* [**`deferCancellableFuture( )`**](Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture-) — convert a Future that returns an Observable into an Observable in a way that monitors the subscription status of the Observable to determine whether to halt work on the Future, but do not attempt to get the returned Observable until a Subscriber subscribes (??)(`rxjava-async`) -* [**`delay( )`**](Observable-Utility-Operators#delay) — shift the emissions from an Observable forward in time by a specified amount -* [**`dematerialize( )`**](Observable-Utility-Operators#dematerialize) — convert a materialized Observable back into its non-materialized form -* [**`distinct( )`**](Filtering-Observables#distinct) — suppress duplicate items emitted by the source Observable -* [**`distinctUntilChanged( )`**](Filtering-Observables#distinctuntilchanged) — suppress duplicate consecutive items emitted by the source Observable -* **`do( )`** (clojure) — _see [**`doOnEach( )`**](Observable-Utility-Operators#dooneach)_ -* [**`doOnCompleted( )`**](Observable-Utility-Operators#dooncompleted) — register an action to take when an Observable completes successfully -* [**`doOnEach( )`**](Observable-Utility-Operators#dooneach) — register an action to take whenever an Observable emits an item -* [**`doOnError( )`**](Observable-Utility-Operators#doonerror) — register an action to take when an Observable completes with an error -* **`doOnNext( )`** — _see [**`doOnEach( )`**](Observable-Utility-Operators#dooneach)_ +* **`aggregate( )`** — _see [**`reduce( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce)_ +* [**`all( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators) — determine whether all items emitted by an Observable meet some criteria +* [**`amb( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — given two or more source Observables, emits all of the items from the first of these Observables to emit an item +* **`ambWith( )`** — _instance version of [**`amb( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators)_ +* [**`and( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#and-then-and-when) — combine the emissions from two or more source Observables into a `Pattern` (`rxjava-joins`) +* **`apply( )`** (scala) — _see [**`create( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#create)_ +* **`asObservable( )`** (kotlin) — _see [**`from( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#from) (et al.)_ +* [**`asyncAction( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators#toasync-or-asyncaction-or-asyncfunc) — convert an Action into an Observable that executes the Action and emits its return value (`rxjava-async`) +* [**`asyncFunc( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators#toasync-or-asyncaction-or-asyncfunc) — convert a function into an Observable that executes the function and emits its return value (`rxjava-async`) +* [**`averageDouble( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#averagedouble) — calculates the average of Doubles emitted by an Observable and emits this average (`rxjava-math`) +* [**`averageFloat( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#averagefloat) — calculates the average of Floats emitted by an Observable and emits this average (`rxjava-math`) +* [**`averageInteger( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) — calculates the average of Integers emitted by an Observable and emits this average (`rxjava-math`) +* [**`averageLong( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) — calculates the average of Longs emitted by an Observable and emits this average (`rxjava-math`) +* **`blocking( )`** (clojure) — _see [**`toBlocking( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators)_ +* [**`buffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#buffer) — periodically gather items from an Observable into bundles and emit these bundles rather than emitting the items one at a time +* [**`byLine( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — converts an Observable of Strings into an Observable of Lines by treating the source sequence as a stream and splitting it on line-endings +* [**`cache( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — remember the sequence of items emitted by the Observable and emit the same sequence to future Subscribers +* [**`cast( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#cast) — cast all items from the source Observable into a particular type before reemitting them +* **`catch( )`** (clojure) — _see [**`onErrorResumeNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#onerrorresumenext)_ +* [**`chunkify( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#chunkify) — returns an iterable that periodically returns a list of items emitted by the source Observable since the last list (??) +* [**`collect( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#collect) — collects items emitted by the source Observable into a single mutable data structure and returns an Observable that emits this structure +* [**`combineLatest( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#combinelatest) — when an item is emitted by either of two Observables, combine the latest item emitted by each Observable via a specified function and emit items based on the results of this function +* **`combineLatestWith( )`** (scala) — _instance version of [**`combineLatest( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#combinelatest)_ +* [**`concat( )`**](http://reactivex.io/documentation/operators/concat.html) — concatenate two or more Observables sequentially +* [**`concatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#concatmap) — transform the items emitted by an Observable into Observables, then flatten this into a single Observable, without interleaving +* **`concatWith( )`** — _instance version of [**`concat( )`**](http://reactivex.io/documentation/operators/concat.html)_ +* [**`connect( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators) — instructs a Connectable Observable to begin emitting items +* **`cons( )`** (clojure) — _see [**`concat( )`**](http://reactivex.io/documentation/operators/concat.html)_ +* [**`contains( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators) — determine whether an Observable emits a particular item or not +* [**`count( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#count) — counts the number of items emitted by an Observable and emits this count +* [**`countLong( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#count) — counts the number of items emitted by an Observable and emits this count +* [**`create( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#create) — create an Observable from scratch by means of a function +* **`cycle( )`** (clojure) — _see [**`repeat( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables)_ +* [**`debounce( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#debounce) — only emit an item from the source Observable after a particular timespan has passed without the Observable emitting any other items +* [**`decode( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — convert a stream of multibyte characters into an Observable that emits byte arrays that respect character boundaries +* [**`defaultIfEmpty( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — emit items from the source Observable, or emit a default item if the source Observable completes after emitting no items +* [**`defer( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#defer) — do not create the Observable until a Subscriber subscribes; create a fresh Observable on each subscription +* [**`deferFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert a Future that returns an Observable into an Observable, but do not attempt to get the Observable that the Future returns until a Subscriber subscribes (`rxjava-async`) +* [**`deferCancellableFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture) — convert a Future that returns an Observable into an Observable in a way that monitors the subscription status of the Observable to determine whether to halt work on the Future, but do not attempt to get the returned Observable until a Subscriber subscribes (??)(`rxjava-async`) +* [**`delay( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — shift the emissions from an Observable forward in time by a specified amount +* [**`dematerialize( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — convert a materialized Observable back into its non-materialized form +* [**`distinct( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#distinct) — suppress duplicate items emitted by the source Observable +* [**`distinctUntilChanged( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#distinctuntilchanged) — suppress duplicate consecutive items emitted by the source Observable +* **`do( )`** (clojure) — _see [**`doOnEach( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ +* [**`doOnCompleted( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an Observable completes successfully +* [**`doOnEach( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take whenever an Observable emits an item +* [**`doOnError( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an Observable completes with an error +* **`doOnNext( )`** — _see [**`doOnEach( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ * **`doOnRequest( )`** — register an action to take when items are requested from an Observable via reactive-pull backpressure (??) -* [**`doOnSubscribe( )`**](Observable-Utility-Operators#doonsubscribe) — register an action to take when an observer subscribes to an Observable -* [**`doOnTerminate( )`**](Observable-Utility-Operators#doonterminate) — register an action to take when an Observable completes, either successfully or with an error -* [**`doOnUnsubscribe( )`**](Observable-Utility-Operators#doonunsubscribe) — register an action to take when an observer unsubscribes from an Observable -* [**`doWhile( )`**](Conditional-and-Boolean-Operators#dowhile) — emit the source Observable's sequence, and then repeat the sequence as long as a condition remains true (`contrib-computation-expressions`) -* **`drop( )`** (scala/clojure) — _see [**`skip( )`**](Filtering-Observables#skip)_ -* **`dropRight( )`** (scala) — _see [**`skipLast( )`**](Filtering-Observables#skiplast)_ -* **`dropUntil( )`** (scala) — _see [**`skipUntil( )`**](Conditional-and-Boolean-Operators#skipuntil)_ -* **`dropWhile( )`** (scala) — _see [**`skipWhile( )`**](Conditional-and-Boolean-Operators#skipwhile)_ -* **`drop-while( )`** (clojure) — _see [**`skipWhile( )`**](Conditional-and-Boolean-Operators#skipwhile)_ -* [**`elementAt( )`**](Filtering-Observables#elementat) — emit item _n_ emitted by the source Observable -* [**`elementAtOrDefault( )`**](Filtering-Observables#elementatordefault) — emit item _n_ emitted by the source Observable, or a default item if the source Observable emits fewer than _n_ items -* [**`empty( )`**](Creating-Observables#empty-error-and-never) — create an Observable that emits nothing and then completes -* [**`encode( )`**](String-Observables#encode) (`StringObservable`) — transform an Observable that emits strings into an Observable that emits byte arrays that respect character boundaries of multibyte characters in the original strings -* [**`error( )`**](Creating-Observables#empty-error-and-never) — create an Observable that emits nothing and then signals an error -* **`every( )`** (clojure) — _see [**`all( )`**](Conditional-and-Boolean-Operators#all)_ -* [**`exists( )`**](Conditional-and-Boolean-Operators#exists-and-isempty) — determine whether an Observable emits any items or not -* [**`filter( )`**](Filtering-Observables#filter) — filter items emitted by an Observable -* **`finally( )`** (clojure) — _see [**`finallyDo( )`**](Observable-Utility-Operators#finallydo)_ -* **`filterNot( )`** (scala) — _see [**`filter( )`**](Filtering-Observables#filter)_ -* [**`finallyDo( )`**](Observable-Utility-Operators#finallydo) — register an action to take when an Observable completes -* [**`first( )`**](Filtering-Observables#first-and-takefirst) (`Observable`) — emit only the first item emitted by an Observable, or the first item that meets some condition -* [**`first( )`**](Blocking-Observable-Operators#first-and-firstordefault) (`BlockingObservable`) — emit only the first item emitted by an Observable, or the first item that meets some condition -* [**`firstOrDefault( )`**](Filtering-Observables#firstordefault) (`Observable`) — emit only the first item emitted by an Observable, or the first item that meets some condition, or a default value if the source Observable is empty -* [**`firstOrDefault( )`**](Blocking-Observable-Operators#first-and-firstordefault) (`BlockingObservable`) — emit only the first item emitted by an Observable, or the first item that meets some condition, or a default value if the source Observable is empty -* **`firstOrElse( )`** (scala) — _see [**`firstOrDefault( )`**](Filtering-Observables#firstordefault) or [**`firstOrDefault( )`**](Blocking-Observable-Operators#first-and-firstordefault) (`BlockingObservable`)_ -* [**`flatMap( )`**](Transforming-Observables#flatmap-concatmap-and-flatmapiterable) — transform the items emitted by an Observable into Observables, then flatten this into a single Observable -* [**`flatMapIterable( )`**](Transforming-Observables#flatmap-concatmap-and-flatmapiterable) — create Iterables corresponding to each emission from a source Observable and merge the results into a single Observable -* **`flatMapIterableWith( )`** (scala) — _instance version of [**`flatMapIterable( )`**](Transforming-Observables#flatmap-concatmap-and-flatmapiterable)_ -* **`flatMapWith( )`** (scala) — _instance version of [**`flatmap( )`**](Transforming-Observables#flatmap-concatmap-and-flatmapiterable)_ -* **`flatten( )`** (scala) — _see [**`merge( )`**](Combining-Observables#merge)_ -* **`flattenDelayError( )`** (scala) — _see [**`mergeDelayError( )`**](Combining-Observables#mergedelayerror)_ -* **`foldLeft( )`** (scala) — _see [**`reduce( )`**](Mathematical-and-Aggregate-Operators#reduce)_ -* **`forall( )`** (scala) — _see [**`all( )`**](Conditional-and-Boolean-Operators#all)_ -* **`forEach( )`** (`Observable`) — _see [**`subscribe( )`**](Observable#onnext-oncompleted-and-onerror)_ -* [**`forEach( )`**](Blocking-Observable-Operators#foreach) (`BlockingObservable`) — invoke a function on each item emitted by the Observable; block until the Observable completes -* [**`forEachFuture( )`**](Async-Operators#foreachfuture) (`Async`) — pass Subscriber methods to an Observable but also have it behave like a Future that blocks until it completes (`rxjava-async`) -* [**`forEachFuture( )`**](Phantom-Operators#foreachfuture) (`BlockingObservable`)— create a futureTask that will invoke a specified function on each item emitted by an Observable (??) -* [**`forIterable( )`**](Phantom-Operators#foriterable) — apply a function to the elements of an Iterable to create Observables which are then concatenated (??) -* [**`from( )`**](Creating-Observables#from) — convert an Iterable, a Future, or an Array into an Observable -* [**`from( )`**](String-Observables#from) (`StringObservable`) — convert a stream of characters or a Reader into an Observable that emits byte arrays or Strings -* [**`fromAction( )`**](Async-Operators#fromaction) — convert an Action into an Observable that invokes the action and emits its result when a Subscriber subscribes (`rxjava-async`) -* [**`fromCallable( )`**](Async-Operators#fromcallable) — convert a Callable into an Observable that invokes the callable and emits its result or exception when a Subscriber subscribes (`rxjava-async`) -* [**`fromCancellableFuture( )`**](Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture-) — convert a Future into an Observable in a way that monitors the subscription status of the Observable to determine whether to halt work on the Future, but do not attempt to get the Future's value until a Subscriber subscribes (??)(`rxjava-async`) -* **`fromFunc0( )`** — _see [**`fromCallable( )`**](Async-Operators#fromcallable) (`rxjava-async`)_ -* [**`fromFuture( )`**](Phantom-Operators#fromfuture) — convert a Future into an Observable, but do not attempt to get the Future's value until a Subscriber subscribes (??) -* [**`fromRunnable( )`**](Async-Operators#fromrunnable) — convert a Runnable into an Observable that invokes the runable and emits its result when a Subscriber subscribes (`rxjava-async`) -* [**`generate( )`**](Phantom-Operators#generate-and-generateabsolutetime) — create an Observable that emits a sequence of items as generated by a function of your choosing (??) -* [**`generateAbsoluteTime( )`**](Phantom-Operators#generate-and-generateabsolutetime) — create an Observable that emits a sequence of items as generated by a function of your choosing, with each item emitted at an item-specific time (??) -* **`generator( )`** (clojure) — _see [**`generate( )`**](Phantom-Operators#generate-and-generateabsolutetime)_ -* [**`getIterator( )`**](Blocking-Observable-Operators#transformations-tofuture-toiterable-and-getiterator) — convert the sequence emitted by the Observable into an Iterator -* [**`groupBy( )`**](Transforming-Observables#groupby) — divide an Observable into a set of Observables that emit groups of items from the original Observable, organized by key -* **`group-by( )`** (clojure) — _see [**`groupBy( )`**](Transforming-Observables#groupby)_ -* [**`groupByUntil( )`**](Phantom-Operators#groupbyuntil) — a variant of the [`groupBy( )`](Transforming-Observables#groupby) operator that closes any open GroupedObservable upon a signal from another Observable (??) -* [**`groupJoin( )`**](Combining-Observables#join-and-groupjoin) — combine the items emitted by two Observables whenever one item from one Observable falls within a window of duration specified by an item emitted by the other Observable -* **`head( )`** (scala) — _see [**`first( )`**](Blocking-Observable-Operators#first-and-firstordefault) (`BlockingObservable`)_ -* **`headOption( )`** (scala) — _see [**`firstOrDefault( )`**](Filtering-Observables#firstordefault) or [**`firstOrDefault( )`**](Blocking-Observable-Operators#first-and-firstordefault) (`BlockingObservable`)_ -* **`headOrElse( )`** (scala) — _see [**`firstOrDefault( )`**](Filtering-Observables#firstordefault) or [**`firstOrDefault( )`**](Blocking-Observable-Operators#first-and-firstordefault) (`BlockingObservable`)_ -* [**`ifThen( )`**](Conditional-and-Boolean-Operators#ifthen) — only emit the source Observable's sequence if a condition is true, otherwise emit an empty or default sequence (`contrib-computation-expressions`) -* [**`ignoreElements( )`**](Filtering-Observables#ignoreelements) — discard the items emitted by the source Observable and only pass through the error or completed notification -* [**`interval( )`**](Creating-Observables#interval) — create an Observable that emits a sequence of integers spaced by a given time interval -* **`into( )`** (clojure) — _see [**`reduce( )`**](Mathematical-and-Aggregate-Operators#reduce)_ -* [**`isEmpty( )`**](Conditional-and-Boolean-Operators#exists-and-isempty) — determine whether an Observable emits any items or not -* **`items( )`** (scala) — _see [**`just( )`**](Creating-Observables#just)_ -* [**`join( )`**](Combining-Observables#join-and-groupjoin) — combine the items emitted by two Observables whenever one item from one Observable falls within a window of duration specified by an item emitted by the other Observable -* [**`join( )`**](String-Observables#join) (`StringObservable`) — converts an Observable that emits a sequence of strings into an Observable that emits a single string that concatenates them all, separating them by a specified string -* [**`just( )`**](Creating-Observables#just) — convert an object into an Observable that emits that object -* [**`last( )`**](Blocking-Observable-Operators#last-and-lastordefault) (`BlockingObservable`) — block until the Observable completes, then return the last item emitted by the Observable -* [**`last( )`**](Filtering-Observables#last) (`Observable`) — emit only the last item emitted by the source Observable -* **`lastOption( )`** (scala) — _see [**`lastOrDefault( )`**](Filtering-Observables#lastOrDefault) or [**`lastOrDefault( )`**](Blocking-Observable-Operators#last-and-lastordefault) (`BlockingObservable`)_ -* [**`lastOrDefault( )`**](Blocking-Observable-Operators#last-and-lastordefault) (`BlockingObservable`) — block until the Observable completes, then return the last item emitted by the Observable or a default item if there is no last item -* [**`lastOrDefault( )`**](Filtering-Observables#lastOrDefault) (`Observable`) — emit only the last item emitted by an Observable, or a default value if the source Observable is empty -* **`lastOrElse( )`** (scala) — _see [**`lastOrDefault( )`**](Filtering-Observables#lastOrDefault) or [**`lastOrDefault( )`**](Blocking-Observable-Operators#last-and-lastordefault) (`BlockingObservable`)_ -* [**`latest( )`**](Blocking-Observable-Operators#latest) — returns an iterable that blocks until or unless the Observable emits an item that has not been returned by the iterable, then returns the latest such item -* **`length( )`** (scala) — _see [**`count( )`**](Mathematical-and-Aggregate-Operators#count-and-countlong)_ -* **`limit( )`** — _see [**`take( )`**](Filtering-Observables#take)_ -* **`longCount( )`** (scala) — _see [**`countLong( )`**](Mathematical-and-Aggregate-Operators#count-and-countlong)_ -* [**`map( )`**](Transforming-Observables#map) — transform the items emitted by an Observable by applying a function to each of them -* **`mapcat( )`** (clojure) — _see [**`concatMap( )`**](Transforming-Observables#flatmap-concatmap-and-flatmapiterable)_ -* **`mapMany( )`** — _see: [**`flatMap( )`**](Transforming-Observables#flatmap-concatmap-and-flatmapiterable)_ -* [**`materialize( )`**](Observable-Utility-Operators#materialize) — convert an Observable into a list of Notifications -* [**`max( )`**](Mathematical-and-Aggregate-Operators#max) — emits the maximum value emitted by a source Observable (`rxjava-math`) -* [**`maxBy( )`**](Mathematical-and-Aggregate-Operators#maxby) — emits the item emitted by the source Observable that has the maximum key value (`rxjava-math`) -* [**`merge( )`**](Combining-Observables#merge) — combine multiple Observables into one -* [**`mergeDelayError( )`**](Combining-Observables#mergedelayerror) — combine multiple Observables into one, allowing error-free Observables to continue before propagating errors -* **`merge-delay-error( )`** (clojure) — _see [**`mergeDelayError( )`**](Combining-Observables#mergedelayerror)_ -* **`mergeMap( )`** * — _see: [**`flatMap( )`**](Transforming-Observables#flatmap-concatmap-and-flatmapiterable)_ -* **`mergeMapIterable( )`** — _see: [**`flatMapIterable( )`**](Transforming-Observables#flatmap-concatmap-and-flatmapiterable)_ -* **`mergeWith( )`** — _instance version of [**`merge( )`**](Combining-Observables#merge)_ -* [**`min( )`**](Mathematical-and-Aggregate-Operators#min) — emits the minimum value emitted by a source Observable (`rxjava-math`) -* [**`minBy( )`**](Mathematical-and-Aggregate-Operators#minby) — emits the item emitted by the source Observable that has the minimum key value (`rxjava-math`) -* [**`mostRecent( )`**](Blocking-Observable-Operators#mostrecent) — returns an iterable that always returns the item most recently emitted by the Observable -* [**`multicast( )`**](Phantom-Operators#multicast) — represents an Observable as a Connectable Observable -* [**`never( )`**](Creating-Observables#empty-error-and-never) — create an Observable that emits nothing at all -* [**`next( )`**](Blocking-Observable-Operators#next) — returns an iterable that blocks until the Observable emits another item, then returns that item -* **`nonEmpty( )`** (scala) — _see [**`isEmpty( )`**](Conditional-and-Boolean-Operators#exists-and-isempty)_ -* **`nth( )`** (clojure) — _see [**`elementAt( )`**](Filtering-Observables#elementat) and [**`elementAtOrDefault( )`**](Filtering-Observables#elementatordefault)_ -* [**`observeOn( )`**](Observable-Utility-Operators#observeon) — specify on which Scheduler a Subscriber should observe the Observable -* [**`ofType( )`**](Filtering-Observables#oftype) — emit only those items from the source Observable that are of a particular class -* [**`onBackpressureBlock( )`**](Backpressure) — block the Observable's thread until the Observer is ready to accept more items from the Observable (??) -* [**`onBackpressureBuffer( )`**](Backpressure) — maintain a buffer of all emissions from the source Observable and emit them to downstream Subscribers according to the requests they generate -* [**`onBackpressureDrop( )`**](Backpressure) — drop emissions from the source Observable unless there is a pending request from a downstream Subscriber, in which case emit enough items to fulfill the request -* [**`onErrorFlatMap( )`**](Phantom-Operators#onerrorflatmap) — instructs an Observable to emit a sequence of items whenever it encounters an error (??) -* [**`onErrorResumeNext( )`**](Error-Handling-Operators#onerrorresumenext) — instructs an Observable to emit a sequence of items if it encounters an error -* [**`onErrorReturn( )`**](Error-Handling-Operators#onerrorreturn) — instructs an Observable to emit a particular item when it encounters an error -* [**`onExceptionResumeNext( )`**](Error-Handling-Operators#onexceptionresumenext) — instructs an Observable to continue emitting items after it encounters an exception (but not another variety of throwable) -* **`orElse( )`** (scala) — _see [**`defaultIfEmpty( )`**](Conditional-and-Boolean-Operators#defaultifempty)_ -* [**`parallel( )`**](Phantom-Operators#parallel) — split the work done on the emissions from an Observable into multiple Observables each operating on its own parallel thread (??) -* [**`parallelMerge( )`**](Phantom-Operators#parallelmerge) — combine multiple Observables into smaller number of Observables (??) -* [**`pivot( )`**](Phantom-Operators#pivot) — combine multiple sets of grouped observables so that they are arranged primarily by group rather than by set (??) -* [**`publish( )`**](Connectable-Observable-Operators#observablepublish) — represents an Observable as a Connectable Observable -* [**`publishLast( )`**](Phantom-Operators#publishlast) — represent an Observable as a Connectable Observable that emits only the last item emitted by the source Observable (??) -* [**`range( )`**](Creating-Observables#range) — create an Observable that emits a range of sequential integers -* [**`reduce( )`**](Mathematical-and-Aggregate-Operators#reduce) — apply a function to each emitted item, sequentially, and emit only the final accumulated value -* **`reductions( )`** (clojure) — _see [**`scan( )`**](Transforming-Observables#scan)_ -* [**`refCount( )`**](Connectable-Observable-Operators#connectableobservablerefcount) — makes a Connectable Observable behave like an ordinary Observable -* [**`repeat( )`**](Creating-Observables#repeat) — create an Observable that emits a particular item or sequence of items repeatedly -* [**`repeatWhen( )`**](Creating-Observables#repeatwhen) — create an Observable that emits a particular item or sequence of items repeatedly, depending on the emissions of a second Observable -* [**`replay( )`**](Connectable-Observable-Operators#observablereplay) — ensures that all Subscribers see the same sequence of emitted items, even if they subscribe after the Observable begins emitting the items -* **`rest( )`** (clojure) — _see [**`next( )`**](Blocking-Observable-Operators#next)_ -* **`return( )`** (clojure) — _see [**`just( )`**](Creating-Observables#just)_ -* [**`retry( )`**](Error-Handling-Operators#retry) — if a source Observable emits an error, resubscribe to it in the hopes that it will complete without error -* [**`retrywhen( )`**](Error-Handling-Operators#retrywhen) — if a source Observable emits an error, pass that error to another Observable to determine whether to resubscribe to the source -* [**`runAsync( )`**](Async-Operators#runasync) — returns a `StoppableObservable` that emits multiple actions as generated by a specified Action on a Scheduler (`rxjava-async`) -* [**`sample( )`**](Filtering-Observables#sample-or-throttlelast) — emit the most recent items emitted by an Observable within periodic time intervals -* [**`scan( )`**](Transforming-Observables#scan) — apply a function to each item emitted by an Observable, sequentially, and emit each successive value -* **`seq( )`** (clojure) — _see [**`getIterator( )`**](Blocking-Observable-Operators#transformations-tofuture-toiterable-and-getiterator)_ -* [**`sequenceEqual( )`**](Conditional-and-Boolean-Operators#sequenceequal) — test the equality of sequences emitted by two Observables -* **`sequenceEqualWith( )`** (scala) — _instance version of [**`sequenceEqual( )`**](Conditional-and-Boolean-Operators#sequenceequal)_ -* [**`serialize( )`**](Observable-Utility-Operators#serialize) — force an Observable to make serialized calls and to be well-behaved -* **`share( )`** — _see [**`refCount( )`**](Connectable-Observable-Operators#connectableobservablerefcount)_ -* [**`single( )`**](Blocking-Observable-Operators#single-and-singleordefault) (`BlockingObservable`) — if the source Observable completes after emitting a single item, return that item, otherwise throw an exception -* [**`single( )`**](Observable-Utility-Operators#single-and-singleordefault) (`Observable`) — if the source Observable completes after emitting a single item, emit that item, otherwise notify of an exception -* **`singleOption( )`** (scala) — _see [**`singleOrDefault( )`**](Blocking-Observable-Operators#single-and-singleordefault) (`BlockingObservable`)_ -* [**`singleOrDefault( )`**](Blocking-Observable-Operators#single-and-singleordefault) (`BlockingObservable`) — if the source Observable completes after emitting a single item, return that item, otherwise return a default item -* [**`singleOrDefault( )`**](Observable-Utility-Operators#single-and-singleordefault) (`Observable`) — if the source Observable completes after emitting a single item, emit that item, otherwise emit a default item -* **`singleOrElse( )`** (scala) — _see [**`singleOrDefault( )`**](Observable-Utility-Operators#single-and-singleordefault)_ -* **`size( )`** (scala) — _see [**`count( )`**](Mathematical-and-Aggregate-Operators#count-and-countlong)_ -* [**`skip( )`**](Filtering-Observables#skip) — ignore the first _n_ items emitted by an Observable -* [**`skipLast( )`**](Filtering-Observables#skiplast) — ignore the last _n_ items emitted by an Observable -* [**`skipUntil( )`**](Conditional-and-Boolean-Operators#skipuntil) — discard items emitted by a source Observable until a second Observable emits an item, then emit the remainder of the source Observable's items -* [**`skipWhile( )`**](Conditional-and-Boolean-Operators#skipwhile) — discard items emitted by an Observable until a specified condition is false, then emit the remainder -* **`sliding( )`** (scala) — _see [**`window( )`**](Transforming-Observables#window)_ -* **`slidingBuffer( )`** (scala) — _see [**`buffer( )`**](Transforming-Observables#buffer)_ -* [**`split( )`**](String-Observables#split) (`StringObservable`) — converts an Observable of Strings into an Observable of Strings that treats the source sequence as a stream and splits it on a specified regex boundary -* [**`start( )`**](Async-Operators#start) — create an Observable that emits the return value of a function (`rxjava-async`) -* [**`startCancellableFuture( )`**](Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture-) — convert a function that returns Future into an Observable that emits that Future's return value in a way that monitors the subscription status of the Observable to determine whether to halt work on the Future (??)(`rxjava-async`) -* [**`startFuture( )`**](Async-Operators#startfuture) — convert a function that returns Future into an Observable that emits that Future's return value (`rxjava-async`) -* [**`startWith( )`**](Combining-Observables#startwith) — emit a specified sequence of items before beginning to emit the items from the Observable -* [**`stringConcat( )`**](String-Observables#stringconcat) (`StringObservable`) — converts an Observable that emits a sequence of strings into an Observable that emits a single string that concatenates them all -* [**`subscribeOn( )`**](Observable-Utility-Operators#subscribeon) — specify which Scheduler an Observable should use when its subscription is invoked -* [**`sumDouble( )`**](Mathematical-and-Aggregate-Operators#suminteger-sumlong-sumfloat-and-sumdouble) — adds the Doubles emitted by an Observable and emits this sum (`rxjava-math`) -* [**`sumFloat( )`**](Mathematical-and-Aggregate-Operators#suminteger-sumlong-sumfloat-and-sumdouble) — adds the Floats emitted by an Observable and emits this sum (`rxjava-math`) -* [**`sumInteger( )`**](Mathematical-and-Aggregate-Operators#suminteger-sumlong-sumfloat-and-sumdouble) — adds the Integers emitted by an Observable and emits this sum (`rxjava-math`) -* [**`sumLong( )`**](Mathematical-and-Aggregate-Operators#suminteger-sumlong-sumfloat-and-sumdouble) — adds the Longs emitted by an Observable and emits this sum (`rxjava-math`) -* **`switch( )`** (scala) — _see [**`switchOnNext( )`**](Combining-Observables#switchonnext)_ -* [**`switchCase( )`**](Conditional-and-Boolean-Operators#switchcase) — emit the sequence from a particular Observable based on the results of an evaluation (`contrib-computation-expressions`) -* [**`switchMap( )`**](Transforming-Observables#switchmap) — transform the items emitted by an Observable into Observables, and mirror those items emitted by the most-recently transformed Observable -* [**`switchOnNext( )`**](Combining-Observables#switchonnext) — convert an Observable that emits Observables into a single Observable that emits the items emitted by the most-recently emitted of those Observables -* **`synchronize( )`** — _see [**`serialize( )`**](Observable-Utility-Operators#serialize)_ -* [**`take( )`**](Filtering-Observables#take) — emit only the first _n_ items emitted by an Observable -* [**`takeFirst( )`**](Filtering-Observables#first-and-takefirst) — emit only the first item emitted by an Observable, or the first item that meets some condition -* [**`takeLast( )`**](Filtering-Observables#takelast) — only emit the last _n_ items emitted by an Observable -* [**`takeLastBuffer( )`**](Filtering-Observables#takelastbuffer) — emit the last _n_ items emitted by an Observable, as a single list item -* **`takeRight( )`** (scala) — _see [**`last( )`**](Filtering-Observables#last) (`Observable`) or [**`takeLast( )`**](Filtering-Observables#takelast)_ -* [**`takeUntil( )`**](Conditional-and-Boolean-Operators#takeuntil) — emits the items from the source Observable until a second Observable emits an item -* [**`takeWhile( )`**](Conditional-and-Boolean-Operators#takewhile) — emit items emitted by an Observable as long as a specified condition is true, then skip the remainder -* **`take-while( )`** (clojure) — _see [**`takeWhile( )`**](Conditional-and-Boolean-Operators#takewhile)_ -* [**`then( )`**](Combining-Observables#and-then-and-when) — transform a series of `Pattern` objects via a `Plan` template (`rxjava-joins`) -* [**`throttleFirst( )`**](Filtering-Observables#throttlefirst) — emit the first items emitted by an Observable within periodic time intervals -* [**`throttleLast( )`**](Filtering-Observables#sample-or-throttlelast) — emit the most recent items emitted by an Observable within periodic time intervals -* [**`throttleWithTimeout( )`**](Filtering-Observables#throttlewithtimeout-or-debounce) — only emit an item from the source Observable after a particular timespan has passed without the Observable emitting any other items -* **`throw( )`** (clojure) — _see [**`error( )`**](Creating-Observables#empty-error-and-never)_ -* [**`timeInterval( )`**](Observable-Utility-Operators#timeinterval) — emit the time lapsed between consecutive emissions of a source Observable -* [**`timeout( )`**](Filtering-Observables#timeout) — emit items from a source Observable, but issue an exception if no item is emitted in a specified timespan -* [**`timer( )`**](Creating-Observables#timer) — create an Observable that emits a single item after a given delay -* [**`timestamp( )`**](Observable-Utility-Operators#timestamp) — attach a timestamp to every item emitted by an Observable -* [**`toAsync( )`**](Async-Operators#toasync-or-asyncaction-or-asyncfunc) — convert a function or Action into an Observable that executes the function and emits its return value (`rxjava-async`) -* [**`toBlocking( )`**](Blocking-Observable-Operators) — transform an Observable into a BlockingObservable -* **`toBlockingObservable( )`** - _see [**`toBlocking( )`**](Blocking-Observable-Operators)_ -* [**`toFuture( )`**](Blocking-Observable-Operators#transformations-tofuture-toiterable-and-getiterator) — convert the Observable into a Future -* [**`toIterable( )`**](Blocking-Observable-Operators#transformations-tofuture-toiterable-and-getiterator) — convert the sequence emitted by the Observable into an Iterable -* **`toIterator( )`** — _see [**`getIterator( )`**](Blocking-Observable-Operators#transformations-tofuture-toiterable-and-getiterator)_ -* [**`toList( )`**](Mathematical-and-Aggregate-Operators#tolist) — collect all items from an Observable and emit them as a single List -* [**`toMap( )`**](Mathematical-and-Aggregate-Operators#tomap-and-tomultimap) — convert the sequence of items emitted by an Observable into a map keyed by a specified key function -* [**`toMultimap( )`**](Mathematical-and-Aggregate-Operators#tomap-and-tomultimap) — convert the sequence of items emitted by an Observable into an ArrayList that is also a map keyed by a specified key function -* **`toSeq( )`** (scala) — _see [**`toList( )`**](Mathematical-and-Aggregate-Operators#tolist)_ -* [**`toSortedList( )`**](Mathematical-and-Aggregate-Operators#tosortedlist) — collect all items from an Observable and emit them as a single, sorted List -* **`tumbling( )`** (scala) — _see [**`window( )`**](Transforming-Observables#window)_ -* **`tumblingBuffer( )`** (scala) — _see [**`buffer( )`**](Transforming-Observables#buffer)_ -* [**`using( )`**](Observable-Utility-Operators#using) — create a disposable resource that has the same lifespan as an Observable -* [**`when( )`**](Combining-Observables#and-then-and-when) — convert a series of `Plan` objects into an Observable (`rxjava-joins`) -* **`where( )`** — _see: [**`filter( )`**](Filtering-Observables#filter)_ -* [**`whileDo( )`**](Conditional-and-Boolean-Operators#whiledo) — if a condition is true, emit the source Observable's sequence and then repeat the sequence as long as the condition remains true (`contrib-computation-expressions`) -* [**`window( )`**](Transforming-Observables#window) — periodically subdivide items from an Observable into Observable windows and emit these windows rather than emitting the items one at a time -* [**`zip( )`**](Combining-Observables#zip) — combine sets of items emitted by two or more Observables together via a specified function and emit items based on the results of this function -* **`zipWith( )`** — _instance version of [**`zip( )`**](Combining-Observables#zip)_ -* **`zipWithIndex( )`** (scala) — _see [**`zip( )`**](Combining-Observables#zip)_ -* **`++`** (scala) — _see [**`concat( )`**](Mathematical-and-Aggregate-Operators#concat)_ -* **`+:`** (scala) — _see [**`startWith( )`**](Combining-Observables#startwith)_ +* [**`doOnSubscribe( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an observer subscribes to an Observable +* [**`doOnTerminate( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an Observable completes, either successfully or with an error +* [**`doOnUnsubscribe( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an observer unsubscribes from an Observable +* [**`doWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators) — emit the source Observable's sequence, and then repeat the sequence as long as a condition remains true (`contrib-computation-expressions`) +* **`drop( )`** (scala/clojure) — _see [**`skip( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#skip)_ +* **`dropRight( )`** (scala) — _see [**`skipLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#skiplast)_ +* **`dropUntil( )`** (scala) — _see [**`skipUntil( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators)_ +* **`dropWhile( )`** (scala) — _see [**`skipWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators)_ +* **`drop-while( )`** (clojure) — _see [**`skipWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#skipwhile)_ +* [**`elementAt( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#elementat) — emit item _n_ emitted by the source Observable +* [**`elementAtOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) — emit item _n_ emitted by the source Observable, or a default item if the source Observable emits fewer than _n_ items +* [**`empty( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#empty) — create an Observable that emits nothing and then completes +* [**`encode( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — transform an Observable that emits strings into an Observable that emits byte arrays that respect character boundaries of multibyte characters in the original strings +* [**`error( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#error) — create an Observable that emits nothing and then signals an error +* **`every( )`** (clojure) — _see [**`all( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators)_ +* [**`exists( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators) — determine whether an Observable emits any items or not +* [**`filter( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#filter) — filter items emitted by an Observable +* **`finally( )`** (clojure) — _see [**`finallyDo( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ +* **`filterNot( )`** (scala) — _see [**`filter( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#filter)_ +* [**`finallyDo( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — register an action to take when an Observable completes +* [**`first( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#first) (`Observable`) — emit only the first item emitted by an Observable, or the first item that meets some condition +* [**`first( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — emit only the first item emitted by an Observable, or the first item that meets some condition +* [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) (`Observable`) — emit only the first item emitted by an Observable, or the first item that meets some condition, or a default value if the source Observable is empty +* [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — emit only the first item emitted by an Observable, or the first item that meets some condition, or a default value if the source Observable is empty +* **`firstOrElse( )`** (scala) — _see [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* [**`flatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap) — transform the items emitted by an Observable into Observables, then flatten this into a single Observable +* [**`flatMapIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmapiterable) — create Iterables corresponding to each emission from a source Observable and merge the results into a single Observable +* **`flatMapIterableWith( )`** (scala) — _instance version of [**`flatMapIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmapiterable)_ +* **`flatMapWith( )`** (scala) — _instance version of [**`flatmap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap)_ +* **`flatten( )`** (scala) — _see [**`merge( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#merge)_ +* **`flattenDelayError( )`** (scala) — _see [**`mergeDelayError( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#mergedelayerror)_ +* **`foldLeft( )`** (scala) — _see [**`reduce( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce)_ +* **`forall( )`** (scala) — _see [**`all( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators)_ +* **`forEach( )`** (`Observable`) — _see [**`subscribe( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable)_ +* [**`forEach( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — invoke a function on each item emitted by the Observable; block until the Observable completes +* [**`forEachFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) (`Async`) — pass Subscriber methods to an Observable but also have it behave like a Future that blocks until it completes (`rxjava-async`) +* [**`forEachFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#foreachfuture) (`BlockingObservable`)— create a futureTask that will invoke a specified function on each item emitted by an Observable (??) +* [**`forIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#foriterable) — apply a function to the elements of an Iterable to create Observables which are then concatenated (??) +* [**`from( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#from) — convert an Iterable, a Future, or an Array into an Observable +* [**`from( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — convert a stream of characters or a Reader into an Observable that emits byte arrays or Strings +* [**`fromAction( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert an Action into an Observable that invokes the action and emits its result when a Subscriber subscribes (`rxjava-async`) +* [**`fromCallable( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert a Callable into an Observable that invokes the callable and emits its result or exception when a Subscriber subscribes (`rxjava-async`) +* [**`fromCancellableFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture) — convert a Future into an Observable in a way that monitors the subscription status of the Observable to determine whether to halt work on the Future, but do not attempt to get the Future's value until a Subscriber subscribes (??)(`rxjava-async`) +* **`fromFunc0( )`** — _see [**`fromCallable( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) (`rxjava-async`)_ +* [**`fromFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#fromfuture) — convert a Future into an Observable, but do not attempt to get the Future's value until a Subscriber subscribes (??) +* [**`fromRunnable( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators#fromrunnable) — convert a Runnable into an Observable that invokes the runable and emits its result when a Subscriber subscribes (`rxjava-async`) +* [**`generate( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#generate-and-generateabsolutetime) — create an Observable that emits a sequence of items as generated by a function of your choosing (??) +* [**`generateAbsoluteTime( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#generate-and-generateabsolutetime) — create an Observable that emits a sequence of items as generated by a function of your choosing, with each item emitted at an item-specific time (??) +* **`generator( )`** (clojure) — _see [**`generate( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#generate-and-generateabsolutetime)_ +* [**`getIterator( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — convert the sequence emitted by the Observable into an Iterator +* [**`groupBy( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#groupby) — divide an Observable into a set of Observables that emit groups of items from the original Observable, organized by key +* **`group-by( )`** (clojure) — _see [**`groupBy( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#groupby)_ +* [**`groupByUntil( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators) — a variant of the [`groupBy( )`](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#groupby) operator that closes any open GroupedObservable upon a signal from another Observable (??) +* [**`groupJoin( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#joins) — combine the items emitted by two Observables whenever one item from one Observable falls within a window of duration specified by an item emitted by the other Observable +* **`head( )`** (scala) — _see [**`first( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* **`headOption( )`** (scala) — _see [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* **`headOrElse( )`** (scala) — _see [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`firstOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* [**`ifThen( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — only emit the source Observable's sequence if a condition is true, otherwise emit an empty or default sequence (`contrib-computation-expressions`) +* [**`ignoreElements( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#ignoreelements) — discard the items emitted by the source Observable and only pass through the error or completed notification +* [**`interval( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#interval) — create an Observable that emits a sequence of integers spaced by a given time interval +* **`into( )`** (clojure) — _see [**`reduce( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce)_ +* [**`isEmpty( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators) — determine whether an Observable emits any items or not +* **`items( )`** (scala) — _see [**`just( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#just)_ +* [**`join( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#joins) — combine the items emitted by two Observables whenever one item from one Observable falls within a window of duration specified by an item emitted by the other Observable +* [**`join( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — converts an Observable that emits a sequence of strings into an Observable that emits a single string that concatenates them all, separating them by a specified string +* [**`just( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#just) — convert an object into an Observable that emits that object +* [**`last( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — block until the Observable completes, then return the last item emitted by the Observable +* [**`last( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#last) (`Observable`) — emit only the last item emitted by the source Observable +* **`lastOption( )`** (scala) — _see [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — block until the Observable completes, then return the last item emitted by the Observable or a default item if there is no last item +* [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) (`Observable`) — emit only the last item emitted by an Observable, or a default value if the source Observable is empty +* **`lastOrElse( )`** (scala) — _see [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) or [**`lastOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`)_ +* [**`latest( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — returns an iterable that blocks until or unless the Observable emits an item that has not been returned by the iterable, then returns the latest such item +* **`length( )`** (scala) — _see [**`count( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#count)_ +* **`limit( )`** — _see [**`take( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#take)_ +* **`longCount( )`** (scala) — _see [**`countLong( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators)_ +* [**`map( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#map) — transform the items emitted by an Observable by applying a function to each of them +* **`mapcat( )`** (clojure) — _see [**`concatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#concatmap)_ +* **`mapMany( )`** — _see: [**`flatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap)_ +* [**`materialize( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — convert an Observable into a list of Notifications +* [**`max( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#max) — emits the maximum value emitted by a source Observable (`rxjava-math`) +* [**`maxBy( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) — emits the item emitted by the source Observable that has the maximum key value (`rxjava-math`) +* [**`merge( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#merge) — combine multiple Observables into one +* [**`mergeDelayError( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#mergedelayerror) — combine multiple Observables into one, allowing error-free Observables to continue before propagating errors +* **`merge-delay-error( )`** (clojure) — _see [**`mergeDelayError( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#mergedelayerror)_ +* **`mergeMap( )`** * — _see: [**`flatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmap)_ +* **`mergeMapIterable( )`** — _see: [**`flatMapIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#flatmapiterable)_ +* **`mergeWith( )`** — _instance version of [**`merge( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#merge)_ +* [**`min( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#min) — emits the minimum value emitted by a source Observable (`rxjava-math`) +* [**`minBy( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators) — emits the item emitted by the source Observable that has the minimum key value (`rxjava-math`) +* [**`mostRecent( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — returns an iterable that always returns the item most recently emitted by the Observable +* [**`multicast( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#multicast) — represents an Observable as a Connectable Observable +* [**`never( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#never) — create an Observable that emits nothing at all +* [**`next( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — returns an iterable that blocks until the Observable emits another item, then returns that item +* **`nonEmpty( )`** (scala) — _see [**`isEmpty( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#boolean-operators)_ +* **`nth( )`** (clojure) — _see [**`elementAt( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#elementat) and [**`elementAtOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables)_ +* [**`observeOn( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — specify on which Scheduler a Subscriber should observe the Observable +* [**`ofType( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#oftype) — emit only those items from the source Observable that are of a particular class +* [**`onBackpressureBlock( )`**](https://github.com/ReactiveX/RxJava/wiki/Backpressure#reactive-pull-backpressure-isnt-magic) — block the Observable's thread until the Observer is ready to accept more items from the Observable (??) +* [**`onBackpressureBuffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Backpressure#reactive-pull-backpressure-isnt-magic) — maintain a buffer of all emissions from the source Observable and emit them to downstream Subscribers according to the requests they generate +* [**`onBackpressureDrop( )`**](https://github.com/ReactiveX/RxJava/wiki/Backpressure#reactive-pull-backpressure-isnt-magic) — drop emissions from the source Observable unless there is a pending request from a downstream Subscriber, in which case emit enough items to fulfill the request +* [**`onErrorFlatMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#onerrorflatmap) — instructs an Observable to emit a sequence of items whenever it encounters an error (??) +* [**`onErrorResumeNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#onerrorresumenext) — instructs an Observable to emit a sequence of items if it encounters an error +* [**`onErrorReturn( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#onerrorreturn) — instructs an Observable to emit a particular item when it encounters an error +* [**`onExceptionResumeNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#onexceptionresumenext) — instructs an Observable to continue emitting items after it encounters an exception (but not another variety of throwable) +* **`orElse( )`** (scala) — _see [**`defaultIfEmpty( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators)_ +* [**`parallel( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#parallel) — split the work done on the emissions from an Observable into multiple Observables each operating on its own parallel thread (??) +* [**`parallelMerge( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#parallelmerge) — combine multiple Observables into smaller number of Observables (??) +* [**`pivot( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#pivot) — combine multiple sets of grouped observables so that they are arranged primarily by group rather than by set (??) +* [**`publish( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators) — represents an Observable as a Connectable Observable +* [**`publishLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#publishlast) — represent an Observable as a Connectable Observable that emits only the last item emitted by the source Observable (??) +* [**`range( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#range) — create an Observable that emits a range of sequential integers +* [**`reduce( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#reduce) — apply a function to each emitted item, sequentially, and emit only the final accumulated value +* **`reductions( )`** (clojure) — _see [**`scan( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#scan)_ +* [**`refCount( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators) — makes a Connectable Observable behave like an ordinary Observable +* [**`repeat( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables) — create an Observable that emits a particular item or sequence of items repeatedly +* [**`repeatWhen( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#repeatwhen) — create an Observable that emits a particular item or sequence of items repeatedly, depending on the emissions of a second Observable +* [**`replay( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators#observablereplay) — ensures that all Subscribers see the same sequence of emitted items, even if they subscribe after the Observable begins emitting the items +* **`rest( )`** (clojure) — _see [**`next( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators#next)_ +* **`return( )`** (clojure) — _see [**`just( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#just)_ +* [**`retry( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#retry) — if a source Observable emits an error, resubscribe to it in the hopes that it will complete without error +* [**`retrywhen( )`**](https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators#retrywhen) — if a source Observable emits an error, pass that error to another Observable to determine whether to resubscribe to the source +* [**`runAsync( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — returns a `StoppableObservable` that emits multiple actions as generated by a specified Action on a Scheduler (`rxjava-async`) +* [**`sample( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#sample) — emit the most recent items emitted by an Observable within periodic time intervals +* [**`scan( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#scan) — apply a function to each item emitted by an Observable, sequentially, and emit each successive value +* **`seq( )`** (clojure) — _see [**`getIterator( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators)_ +* [**`sequenceEqual( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators) — test the equality of sequences emitted by two Observables +* **`sequenceEqualWith( )`** (scala) — _instance version of [**`sequenceEqual( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators)_ +* [**`serialize( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators#serialize) — force an Observable to make serialized calls and to be well-behaved +* **`share( )`** — _see [**`refCount( )`**](https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators)_ +* [**`single( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — if the source Observable completes after emitting a single item, return that item, otherwise throw an exception +* [**`single( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) (`Observable`) — if the source Observable completes after emitting a single item, emit that item, otherwise notify of an exception +* **`singleOption( )`** (scala) — _see [**`singleOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators#single-and-singleordefault) (`BlockingObservable`)_ +* [**`singleOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) (`BlockingObservable`) — if the source Observable completes after emitting a single item, return that item, otherwise return a default item +* [**`singleOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) (`Observable`) — if the source Observable completes after emitting a single item, emit that item, otherwise emit a default item +* **`singleOrElse( )`** (scala) — _see [**`singleOrDefault( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ +* **`size( )`** (scala) — _see [**`count( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#count)_ +* [**`skip( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#skip) — ignore the first _n_ items emitted by an Observable +* [**`skipLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#skiplast) — ignore the last _n_ items emitted by an Observable +* [**`skipUntil( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — discard items emitted by a source Observable until a second Observable emits an item, then emit the remainder of the source Observable's items +* [**`skipWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — discard items emitted by an Observable until a specified condition is false, then emit the remainder +* **`sliding( )`** (scala) — _see [**`window( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#window)_ +* **`slidingBuffer( )`** (scala) — _see [**`buffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#buffer)_ +* [**`split( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — converts an Observable of Strings into an Observable of Strings that treats the source sequence as a stream and splits it on a specified regex boundary +* [**`start( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — create an Observable that emits the return value of a function (`rxjava-async`) +* [**`startCancellableFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Phantom-Operators#fromcancellablefuture-startcancellablefuture-and-defercancellablefuture) — convert a function that returns Future into an Observable that emits that Future's return value in a way that monitors the subscription status of the Observable to determine whether to halt work on the Future (??)(`rxjava-async`) +* [**`startFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert a function that returns Future into an Observable that emits that Future's return value (`rxjava-async`) +* [**`startWith( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#startwith) — emit a specified sequence of items before beginning to emit the items from the Observable +* [**`stringConcat( )`**](https://github.com/ReactiveX/RxJava/wiki/String-Observables) (`StringObservable`) — converts an Observable that emits a sequence of strings into an Observable that emits a single string that concatenates them all +* [**`subscribeOn( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — specify which Scheduler an Observable should use when its subscription is invoked +* [**`sumDouble( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#sumdouble) — adds the Doubles emitted by an Observable and emits this sum (`rxjava-math`) +* [**`sumFloat( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#sumfloat) — adds the Floats emitted by an Observable and emits this sum (`rxjava-math`) +* [**`sumInt( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#sumint) — adds the Integers emitted by an Observable and emits this sum (`rxjava-math`) +* [**`sumLong( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#sumlong) — adds the Longs emitted by an Observable and emits this sum (`rxjava-math`) +* **`switch( )`** (scala) — _see [**`switchOnNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#switchonnext)_ +* [**`switchCase( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — emit the sequence from a particular Observable based on the results of an evaluation (`contrib-computation-expressions`) +* [**`switchMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#switchmap) — transform the items emitted by an Observable into Observables, and mirror those items emitted by the most-recently transformed Observable +* [**`switchOnNext( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#switchonnext) — convert an Observable that emits Observables into a single Observable that emits the items emitted by the most-recently emitted of those Observables +* **`synchronize( )`** — _see [**`serialize( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators)_ +* [**`take( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#take) — emit only the first _n_ items emitted by an Observable +* [**`takeFirst( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) — emit only the first item emitted by an Observable, or the first item that meets some condition +* [**`takeLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#takelast) — only emit the last _n_ items emitted by an Observable +* [**`takeLastBuffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables) — emit the last _n_ items emitted by an Observable, as a single list item +* **`takeRight( )`** (scala) — _see [**`last( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#last) (`Observable`) or [**`takeLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#takelast)_ +* [**`takeUntil( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — emits the items from the source Observable until a second Observable emits an item +* [**`takeWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — emit items emitted by an Observable as long as a specified condition is true, then skip the remainder +* **`take-while( )`** (clojure) — _see [**`takeWhile( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators)_ +* [**`then( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#rxjava-joins) — transform a series of `Pattern` objects via a `Plan` template (`rxjava-joins`) +* [**`throttleFirst( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#throttlefirst) — emit the first items emitted by an Observable within periodic time intervals +* [**`throttleLast( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#throttlelast) — emit the most recent items emitted by an Observable within periodic time intervals +* [**`throttleWithTimeout( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#throttlewithtimeout) — only emit an item from the source Observable after a particular timespan has passed without the Observable emitting any other items +* **`throw( )`** (clojure) — _see [**`error( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#error)_ +* [**`timeInterval( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — emit the time lapsed between consecutive emissions of a source Observable +* [**`timeout( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#timeout) — emit items from a source Observable, but issue an exception if no item is emitted in a specified timespan +* [**`timer( )`**](https://github.com/ReactiveX/RxJava/wiki/Creating-Observables#timer) — create an Observable that emits a single item after a given delay +* [**`timestamp( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — attach a timestamp to every item emitted by an Observable +* [**`toAsync( )`**](https://github.com/ReactiveX/RxJava/wiki/Async-Operators) — convert a function or Action into an Observable that executes the function and emits its return value (`rxjava-async`) +* [**`toBlocking( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — transform an Observable into a BlockingObservable +* **`toBlockingObservable( )`** - _see [**`toBlocking( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators)_ +* [**`toFuture( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — convert the Observable into a Future +* [**`toIterable( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators) — convert the sequence emitted by the Observable into an Iterable +* **`toIterator( )`** — _see [**`getIterator( )`**](https://github.com/ReactiveX/RxJava/wiki/Blocking-Observable-Operators)_ +* [**`toList( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tolist) — collect all items from an Observable and emit them as a single List +* [**`toMap( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tomap) — convert the sequence of items emitted by an Observable into a map keyed by a specified key function +* [**`toMultimap( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tomultimap) — convert the sequence of items emitted by an Observable into an ArrayList that is also a map keyed by a specified key function +* **`toSeq( )`** (scala) — _see [**`toList( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tolist)_ +* [**`toSortedList( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators#tosortedlist) — collect all items from an Observable and emit them as a single, sorted List +* **`tumbling( )`** (scala) — _see [**`window( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#window)_ +* **`tumblingBuffer( )`** (scala) — _see [**`buffer( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#buffer)_ +* [**`using( )`**](https://github.com/ReactiveX/RxJava/wiki/Observable-Utility-Operators) — create a disposable resource that has the same lifespan as an Observable +* [**`when( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#rxjava-joins) — convert a series of `Plan` objects into an Observable (`rxjava-joins`) +* **`where( )`** — _see: [**`filter( )`**](https://github.com/ReactiveX/RxJava/wiki/Filtering-Observables#filter)_ +* [**`whileDo( )`**](https://github.com/ReactiveX/RxJava/wiki/Conditional-and-Boolean-Operators#conditional-operators) — if a condition is true, emit the source Observable's sequence and then repeat the sequence as long as the condition remains true (`contrib-computation-expressions`) +* [**`window( )`**](https://github.com/ReactiveX/RxJava/wiki/Transforming-Observables#window) — periodically subdivide items from an Observable into Observable windows and emit these windows rather than emitting the items one at a time +* [**`zip( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#zip) — combine sets of items emitted by two or more Observables together via a specified function and emit items based on the results of this function +* **`zipWith( )`** — _instance version of [**`zip( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#zip)_ +* **`zipWithIndex( )`** (scala) — _see [**`zip( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#zip)_ +* **`++`** (scala) — _see [**`concat( )`**](https://github.com/ReactiveX/RxJava/wiki/Mathematical-and-Aggregate-Operators)_ +* **`+:`** (scala) — _see [**`startWith( )`**](https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#startwith)_ -(??) — this proposed operator is not part of RxJava 1.0 \ No newline at end of file +(??) — this proposed operator is not part of RxJava 1.0 diff --git a/docs/Backpressure.md b/docs/Backpressure.md index d9e7bfa65a..8feec0d487 100644 --- a/docs/Backpressure.md +++ b/docs/Backpressure.md @@ -20,7 +20,7 @@ Cold Observables are ideal for the reactive pull model of backpressure described Your first line of defense against the problems of over-producing Observables is to use some of the ordinary set of Observable operators to reduce the number of emitted items to a more manageable number. The examples in this section will show how you might use such operators to handle a bursty Observable like the one illustrated in the following marble diagram: -​ +​ By fine-tuning the parameters to these operators you can ensure that a slow-consuming observer is not overwhelmed by a fast-producing Observable. @@ -33,7 +33,7 @@ The following diagrams show how you could use each of these operators on the bur ### sample (or throttleLast) The `sample` operator periodically "dips" into the sequence and emits only the most recently emitted item during each dip: -​ +​ ````groovy Observable burstySampled = bursty.sample(500, TimeUnit.MILLISECONDS); ```` @@ -41,7 +41,7 @@ Observable burstySampled = bursty.sample(500, TimeUnit.MILLISECONDS); ### throttleFirst The `throttleFirst` operator is similar, but emits not the most recently emitted item, but the first item that was emitted after the previous "dip": -​ +​ ````groovy Observable burstyThrottled = bursty.throttleFirst(500, TimeUnit.MILLISECONDS); ```` @@ -49,7 +49,7 @@ Observable burstyThrottled = bursty.throttleFirst(500, TimeUnit.MILLISE ### debounce (or throttleWithTimeout) The `debounce` operator emits only those items from the source Observable that are not followed by another item within a specified duration: -​ +​ ````groovy Observable burstyDebounced = bursty.debounce(10, TimeUnit.MILLISECONDS); ```` @@ -64,14 +64,14 @@ The following diagrams show how you could use each of these operators on the bur You could, for example, close and emit a buffer of items from the bursty Observable periodically, at a regular interval of time: -​ +​ ````groovy Observable> burstyBuffered = bursty.buffer(500, TimeUnit.MILLISECONDS); ```` Or you could get fancy, and collect items in buffers during the bursty periods and emit them at the end of each burst, by using the `debounce` operator to emit a buffer closing indicator to the `buffer` operator: -​ +​ ````groovy // we have to multicast the original bursty Observable so we can use it // both as our source and as the source for our buffer closing selector: @@ -86,14 +86,14 @@ Observable> burstyBuffered = burstyMulticast.buffer(burstyDebounce `window` is similar to `buffer`. One variant of `window` allows you to periodically emit Observable windows of items at a regular interval of time: -​ +​ ````groovy Observable> burstyWindowed = bursty.window(500, TimeUnit.MILLISECONDS); ```` You could also choose to emit a new window each time you have collected a particular number of items from the source Observable: -​ +​ ````groovy Observable> burstyWindowed = bursty.window(5); ```` @@ -158,11 +158,11 @@ For this to work, though, Observables _A_ and _B_ must respond correctly to the
onBackpressureBuffer
-
maintains a buffer of all emissions from the source Observable and emits them to downstream Subscribers according to the requests they generate

an experimental version of this operator (not available in RxJava 1.0) allows you to set the capacity of the buffer; applying this operator will cause the resulting Observable to terminate with an error if this buffer is overrun​
+
maintains a buffer of all emissions from the source Observable and emits them to downstream Subscribers according to the requests they generate

an experimental version of this operator (not available in RxJava 1.0) allows you to set the capacity of the buffer; applying this operator will cause the resulting Observable to terminate with an error if this buffer is overrun​
onBackpressureDrop
-
drops emissions from the source Observable unless there is a pending request from a downstream Subscriber, in which case it will emit enough items to fulfill the request
+
drops emissions from the source Observable unless there is a pending request from a downstream Subscriber, in which case it will emit enough items to fulfill the request
onBackpressureBlock (experimental, not in RxJava 1.0)
-
blocks the thread on which the source Observable is operating until such time as a Subscriber issues a request for items, and then unblocks the thread only so long as there are pending requests
+
blocks the thread on which the source Observable is operating until such time as a Subscriber issues a request for items, and then unblocks the thread only so long as there are pending requests
If you do not apply any of these operators to an Observable that does not support backpressure, _and_ if either you as the Subscriber or some operator between you and the Observable attempts to apply reactive pull backpressure, you will encounter a `MissingBackpressureException` which you will be notified of via your `onError()` callback. @@ -172,4 +172,4 @@ If you do not apply any of these operators to an Observable that does not suppor If the standard operators are providing the expected behavior, [one can write custom operators in RxJava](https://github.com/ReactiveX/RxJava/wiki/Implementing-custom-operators-(draft)). # See also -* [RxJava 0.20.0-RC1 release notes](https://github.com/ReactiveX/RxJava/releases/tag/0.20.0-RC1) \ No newline at end of file +* [RxJava 0.20.0-RC1 release notes](https://github.com/ReactiveX/RxJava/releases/tag/0.20.0-RC1) diff --git a/docs/Creating-Observables.md b/docs/Creating-Observables.md index 8e31a56528..360e03ab09 100644 --- a/docs/Creating-Observables.md +++ b/docs/Creating-Observables.md @@ -37,8 +37,8 @@ There exist overloads with 2 to 9 arguments for convenience, which objects (with ```java Observable observable = Observable.just("1", "A", "3.2", "def"); -observable.subscribe(item -> System.out.print(item), error -> error.printStackTrace, - () -> System.out.println()); + observable.subscribe(item -> System.out.print(item), error -> error.printStackTrace(), + () -> System.out.println()); ``` ## From @@ -80,7 +80,7 @@ for (int i = 0; i < array.length; i++) { array[i] = i; } -Observable observable = Observable.fromIterable(array); +Observable observable = Observable.fromArray(array); observable.subscribe(item -> System.out.println(item), error -> error.printStackTrace(), () -> System.out.println("Done")); @@ -155,7 +155,7 @@ Given a pre-existing, already running or already completed `java.util.concurrent #### fromFuture example: ```java -ScheduledExecutorService executor = Executors.newSingleThreadedScheduledExecutor(); +ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); Future future = executor.schedule(() -> "Hello world!", 1, TimeUnit.SECONDS); @@ -298,10 +298,10 @@ String greeting = "Hello World!"; Observable indexes = Observable.range(0, greeting.length()); -Observable characters = indexes +Observable characters = indexes .map(index -> greeting.charAt(index)); -characters.subscribe(character -> System.out.print(character), erro -> error.printStackTrace(), +characters.subscribe(character -> System.out.print(character), error -> error.printStackTrace(), () -> System.out.println()); ``` @@ -396,7 +396,7 @@ Observable error = Observable.error(new IOException()); error.subscribe( v -> System.out.println("This should never be printed!"), - error -> error.printStackTrace(), + e -> e.printStackTrace(), () -> System.out.println("This neither!")); ``` @@ -423,4 +423,4 @@ for (int i = 0; i < 10; i++) { error -> error.printStackTrace(), () -> System.out.println("Done")); } -``` +``` \ No newline at end of file diff --git a/docs/What's-different-in-2.0.md b/docs/What's-different-in-2.0.md index fac50df56d..30f9a4ccba 100644 --- a/docs/What's-different-in-2.0.md +++ b/docs/What's-different-in-2.0.md @@ -1,6 +1,6 @@ -RxJava 2.0 has been completely rewritten from scratch on top of the Reactive-Streams specification. The specification itself has evolved out of RxJava 1.x and provides a common baseline for reactive systems and libraries. +RxJava 2.0 has been completely rewritten from scratch on top of the Reactive Streams specification. The specification itself has evolved out of RxJava 1.x and provides a common baseline for reactive systems and libraries. -Because Reactive-Streams has a different architecture, it mandates changes to some well known RxJava types. This wiki page attempts to summarize what has changed and describes how to rewrite 1.x code into 2.x code. +Because Reactive Streams has a different architecture, it mandates changes to some well known RxJava types. This wiki page attempts to summarize what has changed and describes how to rewrite 1.x code into 2.x code. For technical details on how to write operators for 2.x, please visit the [Writing Operators](https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0) wiki page. @@ -20,7 +20,7 @@ For technical details on how to write operators for 2.x, please visit the [Writi - [Subscriber](#subscriber) - [Subscription](#subscription) - [Backpressure](#backpressure) - - [Reactive-Streams compliance](#reactive-streams-compliance) + - [Reactive Streams compliance](#reactive-streams-compliance) - [Runtime hooks](#runtime-hooks) - [Error handling](#error-handling) - [Scheduler](#schedulers) @@ -105,7 +105,7 @@ When architecting dataflows (as an end-consumer of RxJava) or deciding upon what # Single -The 2.x `Single` reactive base type, which can emit a single `onSuccess` or `onError` has been redesigned from scratch. Its architecture now derives from the Reactive-Streams design. Its consumer type (`rx.Single.SingleSubscriber`) has been changed from being a class that accepts `rx.Subscription` resources to be an interface `io.reactivex.SingleObserver` that has only 3 methods: +The 2.x `Single` reactive base type, which can emit a single `onSuccess` or `onError` has been redesigned from scratch. Its architecture now derives from the Reactive Streams design. Its consumer type (`rx.Single.SingleSubscriber`) has been changed from being a class that accepts `rx.Subscription` resources to be an interface `io.reactivex.SingleObserver` that has only 3 methods: ```java interface SingleObserver { @@ -119,7 +119,7 @@ and follows the protocol `onSubscribe (onSuccess | onError)?`. # Completable -The `Completable` type remains largely the same. It was already designed along the Reactive-Streams style for 1.x so no user-level changes there. +The `Completable` type remains largely the same. It was already designed along the Reactive Streams style for 1.x so no user-level changes there. Similar to the naming changes, `rx.Completable.CompletableSubscriber` has become `io.reactivex.CompletableObserver` with `onSubscribe(Disposable)`: @@ -154,7 +154,7 @@ Maybe.just(1) # Base reactive interfaces -Following the style of extending the Reactive-Streams `Publisher` in `Flowable`, the other base reactive classes now extend similar base interfaces (in package `io.reactivex`): +Following the style of extending the Reactive Streams `Publisher` in `Flowable`, the other base reactive classes now extend similar base interfaces (in package `io.reactivex`): ```java interface ObservableSource { @@ -182,7 +182,7 @@ Flowable flatMap(Function> mapper Observable flatMap(Function> mapper); ``` -By having `Publisher` as input this way, you can compose with other Reactive-Streams compliant libraries without the need to wrap them or convert them into `Flowable` first. +By having `Publisher` as input this way, you can compose with other Reactive Streams compliant libraries without the need to wrap them or convert them into `Flowable` first. If an operator has to offer a reactive base type, however, the user will receive the full reactive class (as giving out an `XSource` is practically useless as it doesn't have operators on it): @@ -197,7 +197,7 @@ source.compose((Flowable flowable) -> # Subjects and Processors -In the Reactive-Streams specification, the `Subject`-like behavior, namely being a consumer and supplier of events at the same time, is done by the `org.reactivestreams.Processor` interface. As with the `Observable`/`Flowable` split, the backpressure-aware, Reactive-Streams compliant implementations are based on the `FlowableProcessor` class (which extends `Flowable` to give a rich set of instance operators). An important change regarding `Subject`s (and by extension, `FlowableProcessor`) that they no longer support `T -> R` like conversion (that is, input is of type `T` and the output is of type `R`). (We never had a use for it in 1.x and the original `Subject` came from .NET where there is a `Subject` overload because .NET allows the same class name with a different number of type arguments.) +In the Reactive Streams specification, the `Subject`-like behavior, namely being a consumer and supplier of events at the same time, is done by the `org.reactivestreams.Processor` interface. As with the `Observable`/`Flowable` split, the backpressure-aware, Reactive Streams compliant implementations are based on the `FlowableProcessor` class (which extends `Flowable` to give a rich set of instance operators). An important change regarding `Subject`s (and by extension, `FlowableProcessor`) that they no longer support `T -> R` like conversion (that is, input is of type `T` and the output is of type `R`). (We never had a use for it in 1.x and the original `Subject` came from .NET where there is a `Subject` overload because .NET allows the same class name with a different number of type arguments.) The `io.reactivex.subjects.AsyncSubject`, `io.reactivex.subjects.BehaviorSubject`, `io.reactivex.subjects.PublishSubject`, `io.reactivex.subjects.ReplaySubject` and `io.reactivex.subjects.UnicastSubject` in 2.x don't support backpressure (as part of the 2.x `Observable` family). @@ -296,7 +296,7 @@ In addition, operators requiring a predicate no longer use `Func1` b # Subscriber -The Reactive-Streams specification has its own Subscriber as an interface. This interface is lightweight and combines request management with cancellation into a single interface `org.reactivestreams.Subscription` instead of having `rx.Producer` and `rx.Subscription` separately. This allows creating stream consumers with less internal state than the quite heavy `rx.Subscriber` of 1.x. +The Reactive Streams specification has its own Subscriber as an interface. This interface is lightweight and combines request management with cancellation into a single interface `org.reactivestreams.Subscription` instead of having `rx.Producer` and `rx.Subscription` separately. This allows creating stream consumers with less internal state than the quite heavy `rx.Subscriber` of 1.x. ```java Flowable.range(1, 10).subscribe(new Subscriber() { @@ -354,7 +354,7 @@ Flowable.range(1, 10).delay(1, TimeUnit.SECONDS).subscribe(subscriber); subscriber.dispose(); ``` -Note also that due to Reactive-Streams compatibility, the method `onCompleted` has been renamed to `onComplete` without the trailing `d`. +Note also that due to Reactive Streams compatibility, the method `onCompleted` has been renamed to `onComplete` without the trailing `d`. Since 1.x `Observable.subscribe(Subscriber)` returned `Subscription`, users often added the `Subscription` to a `CompositeSubscription` for example: @@ -364,7 +364,7 @@ CompositeSubscription composite = new CompositeSubscription(); composite.add(Observable.range(1, 5).subscribe(new TestSubscriber())); ``` -Due to the Reactive-Streams specification, `Publisher.subscribe` returns void and the pattern by itself no longer works in 2.0. To remedy this, the method `E subscribeWith(E subscriber)` has been added to each base reactive class which returns its input subscriber/observer as is. With the two examples before, the 2.x code can now look like this since `ResourceSubscriber` implements `Disposable` directly: +Due to the Reactive Streams specification, `Publisher.subscribe` returns void and the pattern by itself no longer works in 2.0. To remedy this, the method `E subscribeWith(E subscriber)` has been added to each base reactive class which returns its input subscriber/observer as is. With the two examples before, the 2.x code can now look like this since `ResourceSubscriber` implements `Disposable` directly: ```java CompositeDisposable composite2 = new CompositeDisposable(); @@ -420,11 +420,11 @@ This behavior differs from 1.x where a `request` call went through a deferred lo # Subscription -In RxJava 1.x, the interface `rx.Subscription` was responsible for stream and resource lifecycle management, namely unsubscribing a sequence and releasing general resources such as scheduled tasks. The Reactive-Streams specification took this name for specifying an interaction point between a source and a consumer: `org.reactivestreams.Subscription` allows requesting a positive amount from the upstream and allows cancelling the sequence. +In RxJava 1.x, the interface `rx.Subscription` was responsible for stream and resource lifecycle management, namely unsubscribing a sequence and releasing general resources such as scheduled tasks. The Reactive Streams specification took this name for specifying an interaction point between a source and a consumer: `org.reactivestreams.Subscription` allows requesting a positive amount from the upstream and allows cancelling the sequence. To avoid the name clash, the 1.x `rx.Subscription` has been renamed into `io.reactivex.Disposable` (somewhat resembling .NET's own IDisposable). -Because Reactive-Streams base interface, `org.reactivestreams.Publisher` defines the `subscribe()` method as `void`, `Flowable.subscribe(Subscriber)` no longer returns any `Subscription` (or `Disposable`). The other base reactive types also follow this signature with their respective subscriber types. +Because Reactive Streams base interface, `org.reactivestreams.Publisher` defines the `subscribe()` method as `void`, `Flowable.subscribe(Subscriber)` no longer returns any `Subscription` (or `Disposable`). The other base reactive types also follow this signature with their respective subscriber types. The other overloads of `subscribe` now return `Disposable` in 2.x. @@ -436,19 +436,19 @@ The original `Subscription` container types have been renamed and updated # Backpressure -The Reactive-Streams specification mandates operators supporting backpressure, specifically via the guarantee that they don't overflow their consumers when those don't request. Operators of the new `Flowable` base reactive type now consider downstream request amounts properly, however, this doesn't mean `MissingBackpressureException` is gone. The exception is still there but this time, the operator that can't signal more `onNext` will signal this exception instead (allowing better identification of who is not properly backpressured). +The Reactive Streams specification mandates operators supporting backpressure, specifically via the guarantee that they don't overflow their consumers when those don't request. Operators of the new `Flowable` base reactive type now consider downstream request amounts properly, however, this doesn't mean `MissingBackpressureException` is gone. The exception is still there but this time, the operator that can't signal more `onNext` will signal this exception instead (allowing better identification of who is not properly backpressured). As an alternative, the 2.x `Observable` doesn't do backpressure at all and is available as a choice to switch over. -# Reactive-Streams compliance +# Reactive Streams compliance **updated in 2.0.7** -**The `Flowable`-based sources and operators are, as of 2.0.7, fully Reactive-Streams version 1.0.0 specification compliant.** +**The `Flowable`-based sources and operators are, as of 2.0.7, fully Reactive Streams version 1.0.0 specification compliant.** Before 2.0.7, the operator `strict()` had to be applied in order to achieve the same level of compliance. In 2.0.7, the operator `strict()` returns `this`, is deprecated and will be removed completely in 2.1.0. -As one of the primary goals of RxJava 2, the design focuses on performance and in order enable it, RxJava 2.0.7 adds a custom `io.reactivex.FlowableSubscriber` interface (extends `org.reactivestreams.Subscriber`) but adds no new methods to it. The new interface is **constrained to RxJava 2** and represents a consumer to `Flowable` that is able to work in a mode that relaxes the Reactive-Streams version 1.0.0 specification in rules §1.3, §2.3, §2.12 and §3.9: +As one of the primary goals of RxJava 2, the design focuses on performance and in order enable it, RxJava 2.0.7 adds a custom `io.reactivex.FlowableSubscriber` interface (extends `org.reactivestreams.Subscriber`) but adds no new methods to it. The new interface is **constrained to RxJava 2** and represents a consumer to `Flowable` that is able to work in a mode that relaxes the Reactive Streams version 1.0.0 specification in rules §1.3, §2.3, §2.12 and §3.9: - §1.3 relaxation: `onSubscribe` may run concurrently with `onNext` in case the `FlowableSubscriber` calls `request()` from inside `onSubscribe` and it is the resposibility of `FlowableSubscriber` to ensure thread-safety between the remaining instructions in `onSubscribe` and `onNext`. - §2.3 relaxation: calling `Subscription.cancel` and `Subscription.request` from `FlowableSubscriber.onComplete()` or `FlowableSubscriber.onError()` is considered a no-operation. @@ -603,7 +603,7 @@ Integer i = Flowable.range(100, 100).blockingLast(); (The reason for this is twofold: performance and ease of use of the library as a synchronous Java 8 Streams-like processor.) -Another significant difference between `rx.Subscriber` (and co) and `org.reactivestreams.Subscriber` (and co) is that in 2.x, your `Subscriber`s and `Observer`s are not allowed to throw anything but fatal exceptions (see `Exceptions.throwIfFatal()`). (The Reactive-Streams specification allows throwing `NullPointerException` if the `onSubscribe`, `onNext` or `onError` receives a `null` value, but RxJava doesn't let `null`s in any way.) This means the following code is no longer legal: +Another significant difference between `rx.Subscriber` (and co) and `org.reactivestreams.Subscriber` (and co) is that in 2.x, your `Subscriber`s and `Observer`s are not allowed to throw anything but fatal exceptions (see `Exceptions.throwIfFatal()`). (The Reactive Streams specification allows throwing `NullPointerException` if the `onSubscribe`, `onNext` or `onError` receives a `null` value, but RxJava doesn't let `null`s in any way.) This means the following code is no longer legal: ```java Subscriber subscriber = new Subscriber() { @@ -933,7 +933,7 @@ To make sure the final API of 2.0 is clean as possible, we remove methods and ot ## doOnCancel/doOnDispose/unsubscribeOn -In 1.x, the `doOnUnsubscribe` was always executed on a terminal event because 1.x' `SafeSubscriber` called `unsubscribe` on itself. This was practically unnecessary and the Reactive-Streams specification states that when a terminal event arrives at a `Subscriber`, the upstream `Subscription` should be considered cancelled and thus calling `cancel()` is a no-op. +In 1.x, the `doOnUnsubscribe` was always executed on a terminal event because 1.x' `SafeSubscriber` called `unsubscribe` on itself. This was practically unnecessary and the Reactive Streams specification states that when a terminal event arrives at a `Subscriber`, the upstream `Subscription` should be considered cancelled and thus calling `cancel()` is a no-op. For the same reason, `unsubscribeOn` is not called on the regular termination path but only when there is an actual `cancel` (or `dispose`) call on the chain. diff --git a/docs/What's-different-in-3.0.md b/docs/What's-different-in-3.0.md new file mode 100644 index 0000000000..e936692c06 --- /dev/null +++ b/docs/What's-different-in-3.0.md @@ -0,0 +1,33 @@ +Table of contents + +# Introduction +TBD. + +### API signature changes + +TBD. + +- as() merged into to() +- some operators returning a more appropriate Single or Maybe +- functional interfaces throws widening to Throwable +- standard methods removed +- standard methods signature changes + +### Standardized operators + +(former experimental and beta operators from 2.x) + +TBD. + +### Operator behavior changes + +TBD. + +- connectable sources lifecycle-fixes + + +### Test support changes + +TBD. + +- methods removed from the test consumers diff --git a/docs/Writing-operators-for-2.0.md b/docs/Writing-operators-for-2.0.md index 7b6e57666e..9649c02be7 100644 --- a/docs/Writing-operators-for-2.0.md +++ b/docs/Writing-operators-for-2.0.md @@ -40,7 +40,7 @@ Writing operators, source-like (`fromEmitter`) or intermediate-like (`flatMap`) *(If you have been following [my blog](http://akarnokd.blogspot.hu/) about RxJava internals, writing operators is maybe only 2 times harder than 1.x; some things have moved around, some tools popped up while others have been dropped but there is a relatively straight mapping from 1.x concepts and approaches to 2.x concepts and approaches.)* -In this article, I'll describe the how-to's from the perspective of a developer who skipped the 1.x knowledge base and basically wants to write operators that conforms the Reactive-Streams specification as well as RxJava 2.x's own extensions and additional expectations/requirements. +In this article, I'll describe the how-to's from the perspective of a developer who skipped the 1.x knowledge base and basically wants to write operators that conforms the Reactive Streams specification as well as RxJava 2.x's own extensions and additional expectations/requirements. Since **Reactor 3** has the same architecture as **RxJava 2** (no accident, I architected and contributed 80% of **Reactor 3** as well) the same principles outlined in this page applies to writing operators for **Reactor 3**. Note however that they chose different naming and locations for their utility and support classes so you may have to search for the equivalent components. @@ -66,7 +66,7 @@ When dealing with backpressure in `Flowable` operators, one needs a way to accou The naive approach for accounting would be to simply call `AtomicLong.getAndAdd()` with new requests and `AtomicLong.addAndGet()` for decrementing based on how many elements were emitted. -The problem with this is that the Reactive-Streams specification declares `Long.MAX_VALUE` as the upper bound for outstanding requests (interprets it as the unbounded mode) but adding two large longs may overflow the `long` into a negative value. In addition, if for some reason, there are more values emitted than were requested, the subtraction may yield a negative current request value, causing crashes or hangs. +The problem with this is that the Reactive Streams specification declares `Long.MAX_VALUE` as the upper bound for outstanding requests (interprets it as the unbounded mode) but adding two large longs may overflow the `long` into a negative value. In addition, if for some reason, there are more values emitted than were requested, the subtraction may yield a negative current request value, causing crashes or hangs. Therefore, both addition and subtraction have to be capped at `Long.MAX_VALUE` and `0` respectively. Since there is no dedicated `AtomicLong` method for it, we have to use a Compare-And-Set loop. (Usually, requesting happens relatively rarely compared to emission amounts thus the lack of dedicated machine code instruction is not a performance bottleneck.) @@ -225,7 +225,7 @@ This simplified queue API gets rid of the unused parts (iterator, collections AP ## Deferred actions -The Reactive-Streams has a strict requirement that calling `onSubscribe()` must happen before any calls to the rest of the `onXXX` methods and by nature, any calls to `Subscription.request()` and `Subscription.cancel()`. The same logic applies to the design of `Observable`, `Single`, `Completable` and `Maybe` with their connection type of `Disposable`. +The Reactive Streams has a strict requirement that calling `onSubscribe()` must happen before any calls to the rest of the `onXXX` methods and by nature, any calls to `Subscription.request()` and `Subscription.cancel()`. The same logic applies to the design of `Observable`, `Single`, `Completable` and `Maybe` with their connection type of `Disposable`. Often though, such call to `onSubscribe` may happen later than the respective `cancel()` needs to happen. For example, the user may want to call `cancel()` before the respective `Subscription` actually becomes available in `subscribeOn`. Other operators may need to call `onSubscribe` before they connect to other sources but at that time, there is no direct way for relaying a `cancel` call to an unavailable upstream `Subscription`. @@ -286,7 +286,7 @@ The same pattern applies to `Subscription` with its `cancel()` method and with h ### Deferred requesting -With `Flowable`s (and Reactive-Streams `Publisher`s) the `request()` calls need to be deferred as well. In one form (the simpler one), the respective late `Subscription` will eventually arrive and we need to relay all previous and all subsequent request amount to its `request()` method. +With `Flowable`s (and Reactive Streams `Publisher`s) the `request()` calls need to be deferred as well. In one form (the simpler one), the respective late `Subscription` will eventually arrive and we need to relay all previous and all subsequent request amount to its `request()` method. In 1.x, this behavior was implicitly provided by `rx.Subscriber` but at a high cost that had to be payed by all instances whether or not they needed this feature. @@ -561,7 +561,7 @@ On the fast path, when we try to leave it, it is possible a concurrent call to ` ## FlowableSubscriber -Version 2.0.7 introduced a new interface, `FlowableSubscriber` that extends `Subscriber` from Reactive-Streams. It has the same methods with the same parameter types but different textual rules attached to it, a set of relaxations to the Reactive-Streams specification to enable better performing RxJava internals while still honoring the specification to the letter for non-RxJava consumers of `Flowable`s. +Version 2.0.7 introduced a new interface, `FlowableSubscriber` that extends `Subscriber` from Reactive Streams. It has the same methods with the same parameter types but different textual rules attached to it, a set of relaxations to the Reactive Streams specification to enable better performing RxJava internals while still honoring the specification to the letter for non-RxJava consumers of `Flowable`s. The rule relaxations are as follows: @@ -588,7 +588,7 @@ The other base reactive consumers, `Observer`, `SingleObserver`, `MaybeObserver` # Backpressure and cancellation -Backpressure (or flow control) in Reactive-Streams is the means to tell the upstream how many elements to produce or to tell it to stop producing elements altogether. Unlike the name suggest, there is no physical pressure preventing the upstream from calling `onNext` but the protocol to honor the request amount. +Backpressure (or flow control) in Reactive Streams is the means to tell the upstream how many elements to produce or to tell it to stop producing elements altogether. Unlike the name suggest, there is no physical pressure preventing the upstream from calling `onNext` but the protocol to honor the request amount. ## Replenishing @@ -1425,7 +1425,7 @@ public final class FlowableMyOperator extends Flowable { } ``` -When taking other reactive types as inputs in these operators, it is recommended one defines the base reactive interfaces instead of the abstract classes, allowing better interoperability between libraries (especially with `Flowable` operators and other Reactive-Streams `Publisher`s). To recap, these are the class-interface pairs: +When taking other reactive types as inputs in these operators, it is recommended one defines the base reactive interfaces instead of the abstract classes, allowing better interoperability between libraries (especially with `Flowable` operators and other Reactive Streams `Publisher`s). To recap, these are the class-interface pairs: - `Flowable` - `Publisher` - `FlowableSubscriber`/`Subscriber` - `Observable` - `ObservableSource` - `Observer` @@ -1433,7 +1433,7 @@ When taking other reactive types as inputs in these operators, it is recommended - `Completable` - `CompletableSource` - `CompletableObserver` - `Maybe` - `MaybeSource` - `MaybeObserver` -RxJava 2.x locks down `Flowable.subscribe` (and the same methods in the other types) in order to provide runtime hooks into the various flows, therefore, implementors are given the `subscribeActual()` to be overridden. When it is invoked, all relevant hooks and wrappers have been applied. Implementors should avoid throwing unchecked exceptions as the library generally can't deliver it to the respective `Subscriber` due to lifecycle restrictions of the Reactive-Streams specification and sends it to the global error consumer via `RxJavaPlugins.onError`. +RxJava 2.x locks down `Flowable.subscribe` (and the same methods in the other types) in order to provide runtime hooks into the various flows, therefore, implementors are given the `subscribeActual()` to be overridden. When it is invoked, all relevant hooks and wrappers have been applied. Implementors should avoid throwing unchecked exceptions as the library generally can't deliver it to the respective `Subscriber` due to lifecycle restrictions of the Reactive Streams specification and sends it to the global error consumer via `RxJavaPlugins.onError`. Unlike in 1.x, In the example above, the incoming `Subscriber` is simply used directly for subscribing again (but still at most once) without any kind of wrapping. In 1.x, one needs to call `Subscribers.wrap` to avoid double calls to `onStart` and cause unexpected double initialization or double-requesting. @@ -1516,7 +1516,7 @@ public final class MyOperator implements FlowableOperator { You may recognize that implementing operators via extension or lifting looks quite similar. In both cases, one usually implements a `FlowableSubscriber` (`Observer`, etc) that takes a downstream `Subscriber`, implements the business logic in the `onXXX` methods and somehow (manually or as part of `lift()`'s lifecycle) gets subscribed to an upstream source. -The benefit of applying the Reactive-Streams design to all base reactive types is that each consumer type is now an interface and can be applied to operators that have to extend some class. This was a pain in 1.x because `Subscriber` and `SingleSubscriber` are classes themselves, plus `Subscriber.request()` is a protected-final method and an operator's `Subscriber` can't implement the `Producer` interface at the same time. In 2.x there is no such problem and one can have both `Subscriber`, `Subscription` or even `Observer` together in the same consumer type. +The benefit of applying the Reactive Streams design to all base reactive types is that each consumer type is now an interface and can be applied to operators that have to extend some class. This was a pain in 1.x because `Subscriber` and `SingleSubscriber` are classes themselves, plus `Subscriber.request()` is a protected-final method and an operator's `Subscriber` can't implement the `Producer` interface at the same time. In 2.x there is no such problem and one can have both `Subscriber`, `Subscription` or even `Observer` together in the same consumer type. # Operator fusion @@ -1569,12 +1569,12 @@ This is the level of the **Rx.NET** library (even up to 3.x) that supports compo This is what **RxJava 1.x** is categorized, it supports composition, backpressure and synchronous cancellation along with the ability to lift an operator into a sequence. #### Generation 3 -This is the level of the Reactive-Streams based libraries such as **Reactor 2** and **Akka-Stream**. They are based upon a specification that evolved out of RxJava but left behind its drawbacks (such as the need to return anything from `subscribe()`). This is incompatible with RxJava 1.x and thus 2.x had to be rewritten from scratch. +This is the level of the Reactive Streams based libraries such as **Reactor 2** and **Akka-Stream**. They are based upon a specification that evolved out of RxJava but left behind its drawbacks (such as the need to return anything from `subscribe()`). This is incompatible with RxJava 1.x and thus 2.x had to be rewritten from scratch. #### Generation 4 -This level expands upon the Reactive-Streams interfaces with operator-fusion (in a compatible fashion, that is, op-fusion is optional between two stages and works without them). **Reactor 3** and **RxJava 2** are at this level. The material around **Akka-Stream** mentions operator-fusion as well, however, **Akka-Stream** is not a native Reactive-Streams implementation (requires a materializer to get a `Publisher` out) and as such it is only Gen 3. +This level expands upon the Reactive Streams interfaces with operator-fusion (in a compatible fashion, that is, op-fusion is optional between two stages and works without them). **Reactor 3** and **RxJava 2** are at this level. The material around **Akka-Stream** mentions operator-fusion as well, however, **Akka-Stream** is not a native Reactive Streams implementation (requires a materializer to get a `Publisher` out) and as such it is only Gen 3. -There are discussions among the 4th generation library providers to have the elements of operator-fusion standardized in Reactive-Streams 2.0 specification (or in a neighboring extension) and have **RxJava 3** and **Reactor 4** work together on that aspect as well. +There are discussions among the 4th generation library providers to have the elements of operator-fusion standardized in Reactive Streams 2.0 specification (or in a neighboring extension) and have **RxJava 3** and **Reactor 4** work together on that aspect as well. ## Components @@ -1629,7 +1629,7 @@ The reason for the two separate interfaces is that if a source is constant, like `Callable` denotes sources, such as `fromCallable` that indicates the single value has to be calculated at runtime of the flow. By this logic, you can see that `ScalarCallable` is a `Callable` on its own right because the constant can be "calculated" as late as the runtime phase of the flow. -Since Reactive-Streams forbids using `null`s as emission values, we can use `null` in `(Scalar)Callable` marked sources to indicate there is no value to be emitted, thus one can't mistake an user's `null` with the empty indicator `null`. For example, this is how `empty()` is implemented: +Since Reactive Streams forbids using `null`s as emission values, we can use `null` in `(Scalar)Callable` marked sources to indicate there is no value to be emitted, thus one can't mistake an user's `null` with the empty indicator `null`. For example, this is how `empty()` is implemented: ```java final class FlowableEmpty extends Flowable implements ScalarCallable { diff --git a/gradle/javadoc_cleanup.gradle b/gradle/javadoc_cleanup.gradle index 79f4d5411a..518d7a1d51 100644 --- a/gradle/javadoc_cleanup.gradle +++ b/gradle/javadoc_cleanup.gradle @@ -12,6 +12,8 @@ task javadocCleanup(dependsOn: "javadoc") doLast { fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/subjects/ReplaySubject.html')); fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/processors/ReplayProcessor.html')); fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/plugins/RxJavaPlugins.html')); + + fixJavadocFile(rootProject.file('build/docs/javadoc/io/reactivex/parallel/ParallelFlowable.html')); } def fixJavadocFile(file) { diff --git a/src/main/java/io/reactivex/Completable.java b/src/main/java/io/reactivex/Completable.java index 7ac7ead344..1994632307 100644 --- a/src/main/java/io/reactivex/Completable.java +++ b/src/main/java/io/reactivex/Completable.java @@ -508,6 +508,13 @@ public static Completable fromMaybe(final MaybeSource maybe) { *
*
Scheduler:
*
{@code fromRunnable} does not operate by default on a particular {@link Scheduler}.
+ *
Error handling:
+ *
If the {@link Runnable} throws an exception, the respective {@link Throwable} is + * delivered to the downstream via {@link CompletableObserver#onError(Throwable)}, + * except when the downstream has disposed this {@code Completable} source. + * In this latter case, the {@code Throwable} is delivered to the global error handler via + * {@link RxJavaPlugins#onError(Throwable)} as an {@link io.reactivex.exceptions.UndeliverableException UndeliverableException}. + *
*
* @param run the runnable to run for each subscriber * @return the new Completable instance @@ -550,7 +557,7 @@ public static Completable fromObservable(final ObservableSource observabl * *

* The {@link Publisher} must follow the - * Reactive-Streams specification. + * Reactive Streams specification. * Violating the specification may result in undefined behavior. *

* If possible, use {@link #create(CompletableOnSubscribe)} to create a @@ -2083,7 +2090,7 @@ public final Completable retry(BiPredicate p *

Scheduler:
*
{@code retry} does not operate by default on a particular {@link Scheduler}.
* - * @param times the number of times the returned Completable should retry this Completable + * @param times the number of times to resubscribe if the current Completable fails * @return the new Completable instance * @throws IllegalArgumentException if times is negative */ @@ -2103,7 +2110,7 @@ public final Completable retry(long times) { *
{@code retry} does not operate by default on a particular {@link Scheduler}.
* *

History: 2.1.8 - experimental - * @param times the number of times the returned Completable should retry this Completable + * @param times the number of times to resubscribe if the current Completable fails * @param predicate the predicate that is called with the latest throwable and should return * true to indicate the returned Completable should resubscribe to this Completable. * @return the new Completable instance @@ -2292,7 +2299,7 @@ public final Disposable subscribe() { @SchedulerSupport(SchedulerSupport.NONE) @Override public final void subscribe(CompletableObserver observer) { - ObjectHelper.requireNonNull(observer, "s is null"); + ObjectHelper.requireNonNull(observer, "observer is null"); try { observer = RxJavaPlugins.onSubscribe(this, observer); diff --git a/src/main/java/io/reactivex/Flowable.java b/src/main/java/io/reactivex/Flowable.java index 79f4251ddd..41cde991a7 100644 --- a/src/main/java/io/reactivex/Flowable.java +++ b/src/main/java/io/reactivex/Flowable.java @@ -17,7 +17,6 @@ import org.reactivestreams.*; -import io.reactivex.Observable; import io.reactivex.annotations.*; import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.Exceptions; @@ -37,12 +36,12 @@ import io.reactivex.subscribers.*; /** - * The Flowable class that implements the Reactive-Streams Pattern and offers factory methods, - * intermediate operators and the ability to consume reactive dataflows. + * The Flowable class that implements the Reactive Streams + * Pattern and offers factory methods, intermediate operators and the ability to consume reactive dataflows. *

- * Reactive-Streams operates with {@code Publisher}s which {@code Flowable} extends. Many operators + * Reactive Streams operates with {@link Publisher}s which {@code Flowable} extends. Many operators * therefore accept general {@code Publisher}s directly and allow direct interoperation with other - * Reactive-Streams implementations. + * Reactive Streams implementations. *

* The Flowable hosts the default buffer size of 128 elements for operators, accessible via {@link #bufferSize()}, * that can be overridden globally via the system parameter {@code rx2.buffer-size}. Most operators, however, have @@ -52,11 +51,103 @@ *

* *

+ * The {@code Flowable} follows the protocol + *


+ * onSubscribe onNext* (onError | onComplete)?
+ * 
+ * where the stream can be disposed through the {@link Subscription} instance provided to consumers through + * {@link Subscriber#onSubscribe(Subscription)}. + * Unlike the {@code Observable.subscribe()} of version 1.x, {@link #subscribe(Subscriber)} does not allow external cancellation + * of a subscription and the {@link Subscriber} instance is expected to expose such capability if needed. + *

+ * Flowables support backpressure and require {@link Subscriber}s to signal demand via {@link Subscription#request(long)}. + *

+ * Example: + *


+ * Disposable d = Flowable.just("Hello world!")
+ * .delay(1, TimeUnit.SECONDS)
+ * .subscribeWith(new DisposableSubscriber<String>() {
+ * @Override public void onStart() {
+ * System.out.println("Start!");
+ * request(1);
+ * }
+ * @Override public void onNext(String t) {
+ * System.out.println(t);
+ * request(1);
+ * }
+ * @Override public void onError(Throwable t) {
+ * t.printStackTrace();
+ * }
+ * @Override public void onComplete() {
+ * System.out.println("Done!");
+ * }
+ * });
+ *
+ * Thread.sleep(500);
+ * // the sequence can now be cancelled via dispose()
+ * d.dispose();
+ * 
+ *

+ * The Reactive Streams specification is relatively strict when defining interactions between {@code Publisher}s and {@code Subscriber}s, so much so + * that there is a significant performance penalty due certain timing requirements and the need to prepare for invalid + * request amounts via {@link Subscription#request(long)}. + * Therefore, RxJava has introduced the {@link FlowableSubscriber} interface that indicates the consumer can be driven with relaxed rules. + * All RxJava operators are implemented with these relaxed rules in mind. + * If the subscribing {@code Subscriber} does not implement this interface, for example, due to it being from another Reactive Streams compliant + * library, the Flowable will automatically apply a compliance wrapper around it. + *

+ * {@code Flowable} is an abstract class, but it is not advised to implement sources and custom operators by extending the class directly due + * to the large amounts of Reactive Streams + * rules to be followed to the letter. See the wiki for + * some guidance if such custom implementations are necessary. + *

+ * The recommended way of creating custom {@code Flowable}s is by using the {@link #create(FlowableOnSubscribe, BackpressureStrategy)} factory method: + *


+ * Flowable<String> source = Flowable.create(new FlowableOnSubscribe<String>() {
+ * @Override
+ * public void subscribe(FlowableEmitter<String> emitter) throws Exception {
+ *
+ * // signal an item
+ * emitter.onNext("Hello");
+ *
+ * // could be some blocking operation
+ * Thread.sleep(1000);
+ *
+ * // the consumer might have cancelled the flow
+ * if (emitter.isCancelled() {
+ * return;
+ * }
+ *
+ * emitter.onNext("World");
+ *
+ * Thread.sleep(1000);
+ *
+ * // the end-of-sequence has to be signaled, otherwise the
+ * // consumers may never finish
+ * emitter.onComplete();
+ * }
+ * }, BackpressureStrategy.BUFFER);
+ *
+ * System.out.println("Subscribe!");
+ * 
+ * source.subscribe(System.out::println);
+ * 
+ * System.out.println("Done!");
+ * 
+ *

+ * RxJava reactive sources, such as {@code Flowable}, are generally synchronous and sequential in nature. In the ReactiveX design, the location (thread) + * where operators run is orthogonal to when the operators can work with data. This means that asynchrony and parallelism + * has to be explicitly expressed via operators such as {@link #subscribeOn(Scheduler)}, {@link #observeOn(Scheduler)} and {@link #parallel()}. In general, + * operators featuring a {@link Scheduler} parameter are introducing this type of asynchrony into the flow. + *

* For more information see the ReactiveX * documentation. * * @param * the type of the items emitted by the Flowable + * @see Observable + * @see ParallelFlowable + * @see io.reactivex.subscribers.DisposableSubscriber */ public abstract class Flowable implements Publisher { /** The default buffer size. */ @@ -1900,7 +1991,7 @@ public static Flowable empty() { @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable error(Callable supplier) { - ObjectHelper.requireNonNull(supplier, "errorSupplier is null"); + ObjectHelper.requireNonNull(supplier, "supplier is null"); return RxJavaPlugins.onAssembly(new FlowableError(supplier)); } @@ -2200,11 +2291,11 @@ public static Flowable fromIterable(Iterable source) { } /** - * Converts an arbitrary Reactive-Streams Publisher into a Flowable if not already a + * Converts an arbitrary Reactive Streams Publisher into a Flowable if not already a * Flowable. *

* The {@link Publisher} must follow the - * Reactive-Streams specification. + * Reactive Streams specification. * Violating the specification may result in undefined behavior. *

* If possible, use {@link #create(FlowableOnSubscribe, BackpressureStrategy)} to create a @@ -2235,7 +2326,7 @@ public static Flowable fromPublisher(final Publisher source) if (source instanceof Flowable) { return RxJavaPlugins.onAssembly((Flowable)source); } - ObjectHelper.requireNonNull(source, "publisher is null"); + ObjectHelper.requireNonNull(source, "source is null"); return RxJavaPlugins.onAssembly(new FlowableFromPublisher(source)); } @@ -2662,8 +2753,8 @@ public static Flowable just(T item) { @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item1, T item2) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); return fromArray(item1, item2); } @@ -2696,9 +2787,9 @@ public static Flowable just(T item1, T item2) { @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item1, T item2, T item3) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); return fromArray(item1, item2, item3); } @@ -2733,10 +2824,10 @@ public static Flowable just(T item1, T item2, T item3) { @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item1, T item2, T item3, T item4) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); return fromArray(item1, item2, item3, item4); } @@ -2773,11 +2864,11 @@ public static Flowable just(T item1, T item2, T item3, T item4) { @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item1, T item2, T item3, T item4, T item5) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); return fromArray(item1, item2, item3, item4, item5); } @@ -2816,12 +2907,12 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5) @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); return fromArray(item1, item2, item3, item4, item5, item6); } @@ -2862,13 +2953,13 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5, @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7); } @@ -2911,14 +3002,14 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5, @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8); } @@ -2963,15 +3054,15 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5, @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); - ObjectHelper.requireNonNull(item9, "The ninth is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9); } @@ -3018,16 +3109,16 @@ public static Flowable just(T item1, T item2, T item3, T item4, T item5, @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public static Flowable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9, T item10) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); - ObjectHelper.requireNonNull(item9, "The ninth item is null"); - ObjectHelper.requireNonNull(item10, "The tenth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); + ObjectHelper.requireNonNull(item10, "item10 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10); } @@ -4386,7 +4477,7 @@ public static Flowable timer(long delay, TimeUnit unit, Scheduler schedule /** * Create a Flowable by wrapping a Publisher which has to be implemented according - * to the Reactive-Streams specification by handling backpressure and + * to the Reactive Streams specification by handling backpressure and * cancellation correctly; no safeguards are provided by the Flowable itself. *

*
Backpressure:
@@ -4485,7 +4576,7 @@ public static Flowable using(Callable resourceSupplier, Consumer resourceDisposer, boolean eager) { ObjectHelper.requireNonNull(resourceSupplier, "resourceSupplier is null"); ObjectHelper.requireNonNull(sourceSupplier, "sourceSupplier is null"); - ObjectHelper.requireNonNull(resourceDisposer, "disposer is null"); + ObjectHelper.requireNonNull(resourceDisposer, "resourceDisposer is null"); return RxJavaPlugins.onAssembly(new FlowableUsing(resourceSupplier, sourceSupplier, resourceDisposer, eager)); } @@ -8175,7 +8266,6 @@ public final Single contains(final Object item) { * @return a Single that emits a single item: the number of items emitted by the source Publisher as a * 64-bit Long item * @see ReactiveX operators documentation: Count - * @see #count() */ @CheckReturnValue @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @@ -8338,7 +8428,7 @@ public final Flowable debounce(long timeout, TimeUnit unit, Scheduler schedul @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable defaultIfEmpty(T defaultItem) { - ObjectHelper.requireNonNull(defaultItem, "item is null"); + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); return switchIfEmpty(just(defaultItem)); } @@ -9167,7 +9257,7 @@ private Flowable doOnEach(Consumer onNext, Consumer doOnEach(final Consumer> onNotification) { - ObjectHelper.requireNonNull(onNotification, "consumer is null"); + ObjectHelper.requireNonNull(onNotification, "onNotification is null"); return doOnEach( Functions.notificationOnNext(onNotification), Functions.notificationOnError(onNotification), @@ -11499,6 +11589,11 @@ public final Flowable mergeWith(@NonNull CompletableSource other) { * asynchronous. If strict event ordering is required, consider using the {@link #observeOn(Scheduler, boolean)} overload. *

* + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. *

*
Backpressure:
*
This operator honors backpressure from downstream and expects it from the source {@code Publisher}. Violating this @@ -11519,6 +11614,7 @@ public final Flowable mergeWith(@NonNull CompletableSource other) { * @see #subscribeOn * @see #observeOn(Scheduler, boolean) * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -11532,6 +11628,11 @@ public final Flowable observeOn(Scheduler scheduler) { * asynchronously with a bounded buffer and optionally delays onError notifications. *

* + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. *

*
Backpressure:
*
This operator honors backpressure from downstream and expects it from the source {@code Publisher}. Violating this @@ -11556,6 +11657,7 @@ public final Flowable observeOn(Scheduler scheduler) { * @see #subscribeOn * @see #observeOn(Scheduler) * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler, boolean) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -11569,6 +11671,11 @@ public final Flowable observeOn(Scheduler scheduler, boolean delayError) { * asynchronously with a bounded buffer of configurable size and optionally delays onError notifications. *

* + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. *

*
Backpressure:
*
This operator honors backpressure from downstream and expects it from the source {@code Publisher}. Violating this @@ -11594,6 +11701,7 @@ public final Flowable observeOn(Scheduler scheduler, boolean delayError) { * @see #subscribeOn * @see #observeOn(Scheduler) * @see #observeOn(Scheduler, boolean) + * @see #delay(long, TimeUnit, Scheduler, boolean) */ @CheckReturnValue @NonNull @@ -11769,7 +11877,7 @@ public final Flowable onBackpressureBuffer(int capacity, boolean delayError) @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable onBackpressureBuffer(int capacity, boolean delayError, boolean unbounded) { - ObjectHelper.verifyPositive(capacity, "bufferSize"); + ObjectHelper.verifyPositive(capacity, "capacity"); return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBuffer(this, capacity, unbounded, delayError, Functions.EMPTY_ACTION)); } @@ -11877,7 +11985,7 @@ public final Flowable onBackpressureBuffer(int capacity, Action onOverflow) { @BackpressureSupport(BackpressureKind.SPECIAL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable onBackpressureBuffer(long capacity, Action onOverflow, BackpressureOverflowStrategy overflowStrategy) { - ObjectHelper.requireNonNull(overflowStrategy, "strategy is null"); + ObjectHelper.requireNonNull(overflowStrategy, "overflowStrategy is null"); ObjectHelper.verifyPositive(capacity, "capacity"); return RxJavaPlugins.onAssembly(new FlowableOnBackpressureBufferStrategy(this, capacity, onOverflow, overflowStrategy)); } @@ -13399,7 +13507,7 @@ public final Flowable retry(BiPredicate p *
* * @param count - * number of retry attempts before failing + * the number of times to resubscribe if the current Flowable fails * @return the source Publisher modified with retry logic * @see ReactiveX operators documentation: Retry */ @@ -13420,7 +13528,7 @@ public final Flowable retry(long count) { *
Scheduler:
*
{@code retry} does not operate by default on a particular {@link Scheduler}.
*
- * @param times the number of times to repeat + * @param times the number of times to resubscribe if the current Flowable fails * @param predicate the predicate called with the failure Throwable and should return true to trigger a retry. * @return the new Flowable instance */ @@ -13570,7 +13678,7 @@ public final Flowable retryWhen( * Subscribes to the current Flowable and wraps the given Subscriber into a SafeSubscriber * (if not already a SafeSubscriber) that * deals with exceptions thrown by a misbehaving Subscriber (that doesn't follow the - * Reactive-Streams specification). + * Reactive Streams specification). *
*
Backpressure:
*
This operator leaves the reactive world and the backpressure behavior depends on the Subscriber's behavior.
@@ -13882,7 +13990,7 @@ public final Flowable scan(BiFunction accumulator) { @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable scan(final R initialValue, BiFunction accumulator) { - ObjectHelper.requireNonNull(initialValue, "seed is null"); + ObjectHelper.requireNonNull(initialValue, "initialValue is null"); return scanWith(Functions.justCallable(initialValue), accumulator); } @@ -14562,7 +14670,7 @@ public final Flowable startWith(Publisher other) { @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) public final Flowable startWith(T value) { - ObjectHelper.requireNonNull(value, "item is null"); + ObjectHelper.requireNonNull(value, "value is null"); return concatArray(just(value), this); } @@ -14793,7 +14901,7 @@ public final void subscribe(Subscriber s) { * If the {@link Flowable} rejects the subscription attempt or otherwise fails it will signal * the error via {@link FlowableSubscriber#onError(Throwable)}. *

- * This subscribe method relaxes the following Reactive-Streams rules: + * This subscribe method relaxes the following Reactive Streams rules: *

    *
  • §1.3: onNext should not be called concurrently until onSubscribe returns. * FlowableSubscriber.onSubscribe should make sure a sync or async call triggered by request() is safe.
  • @@ -15012,6 +15120,7 @@ public final Flowable switchIfEmpty(Publisher other) { * Publisher * @return a Flowable that emits the items emitted by the Publisher returned from applying {@code func} to the most recently emitted item emitted by the source Publisher * @see ReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -15047,6 +15156,7 @@ public final Flowable switchMap(FunctionReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function, int) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.FULL) @@ -15136,7 +15246,7 @@ public final Completable switchMapCompletable(@NonNull FunctionReactiveX operators documentation: FlatMap + * @see #switchMap(Function) * @since 2.0 */ @CheckReturnValue @@ -15211,6 +15322,7 @@ public final Flowable switchMapDelayError(FunctionReactiveX operators documentation: FlatMap + * @see #switchMap(Function, int) * @since 2.0 */ @CheckReturnValue @@ -15264,6 +15376,7 @@ Flowable switchMap0(Function> * and get subscribed to. * @return the new Flowable instance * @see #switchMapMaybe(Function) + * @see #switchMapMaybeDelayError(Function) * @since 2.2 */ @CheckReturnValue @@ -15335,7 +15448,7 @@ public final Flowable switchMapMaybeDelayError(@NonNull FunctionHistory: 2.0.7 - experimental; 2.1 - beta diff --git a/src/main/java/io/reactivex/Maybe.java b/src/main/java/io/reactivex/Maybe.java index ab221724eb..aecb501348 100644 --- a/src/main/java/io/reactivex/Maybe.java +++ b/src/main/java/io/reactivex/Maybe.java @@ -2489,7 +2489,7 @@ public final Single contains(final Object item) { } /** - * Returns a Maybe that counts the total number of items emitted (0 or 1) by the source Maybe and emits + * Returns a Single that counts the total number of items emitted (0 or 1) by the source Maybe and emits * this count as a 64-bit Long. *

    * @@ -2533,7 +2533,7 @@ public final Single count() { @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe defaultIfEmpty(T defaultItem) { - ObjectHelper.requireNonNull(defaultItem, "item is null"); + ObjectHelper.requireNonNull(defaultItem, "defaultItem is null"); return switchIfEmpty(just(defaultItem)); } @@ -2709,7 +2709,7 @@ public final Maybe delaySubscription(long delay, TimeUnit unit, Scheduler sch @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe doAfterSuccess(Consumer onAfterSuccess) { - ObjectHelper.requireNonNull(onAfterSuccess, "doAfterSuccess is null"); + ObjectHelper.requireNonNull(onAfterSuccess, "onAfterSuccess is null"); return RxJavaPlugins.onAssembly(new MaybeDoAfterSuccess(this, onAfterSuccess)); } @@ -2937,7 +2937,7 @@ public final Maybe doOnTerminate(final Action onTerminate) { public final Maybe doOnSuccess(Consumer onSuccess) { return RxJavaPlugins.onAssembly(new MaybePeek(this, Functions.emptyConsumer(), // onSubscribe - ObjectHelper.requireNonNull(onSuccess, "onSubscribe is null"), + ObjectHelper.requireNonNull(onSuccess, "onSuccess is null"), Functions.emptyConsumer(), // onError Functions.EMPTY_ACTION, // onComplete Functions.EMPTY_ACTION, // (onSuccess | onError | onComplete) @@ -3452,7 +3452,7 @@ public final Single isEmpty() { @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Maybe lift(final MaybeOperator lift) { - ObjectHelper.requireNonNull(lift, "onLift is null"); + ObjectHelper.requireNonNull(lift, "lift is null"); return RxJavaPlugins.onAssembly(new MaybeLift(this, lift)); } @@ -3685,7 +3685,7 @@ public final Single toSingle() { *

    Scheduler:
    *
    {@code onErrorComplete} does not operate by default on a particular {@link Scheduler}.
    *
- * @return the new Completable instance + * @return the new Maybe instance */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -3702,7 +3702,7 @@ public final Maybe onErrorComplete() { *
* @param predicate the predicate to call when an Throwable is emitted which should return true * if the Throwable should be swallowed and replaced with an onComplete. - * @return the new Completable instance + * @return the new Maybe instance */ @CheckReturnValue @NonNull @@ -3984,7 +3984,7 @@ public final Flowable repeatWhen(final Function{@code retry} does not operate by default on a particular {@link Scheduler}. *
* - * @return the nww Maybe instance + * @return the new Maybe instance * @see ReactiveX operators documentation: Retry */ @CheckReturnValue @@ -4006,7 +4006,7 @@ public final Maybe retry() { * @param predicate * the predicate that determines if a resubscription may happen in case of a specific exception * and retry count - * @return the nww Maybe instance + * @return the new Maybe instance * @see #retry() * @see ReactiveX operators documentation: Retry */ @@ -4031,7 +4031,7 @@ public final Maybe retry(BiPredicate pred * * * @param count - * number of retry attempts before failing + * the number of times to resubscribe if the current Maybe fails * @return the new Maybe instance * @see ReactiveX operators documentation: Retry */ @@ -4048,7 +4048,7 @@ public final Maybe retry(long count) { *
Scheduler:
*
{@code retry} does not operate by default on a particular {@link Scheduler}.
* - * @param times the number of times to repeat + * @param times the number of times to resubscribe if the current Maybe fails * @param predicate the predicate called with the failure Throwable and should return true to trigger a retry. * @return the new Maybe instance */ @@ -4512,7 +4512,7 @@ public final Maybe timeout(long timeout, TimeUnit timeUnit) { @NonNull @SchedulerSupport(SchedulerSupport.COMPUTATION) public final Maybe timeout(long timeout, TimeUnit timeUnit, MaybeSource fallback) { - ObjectHelper.requireNonNull(fallback, "other is null"); + ObjectHelper.requireNonNull(fallback, "fallback is null"); return timeout(timeout, timeUnit, Schedulers.computation(), fallback); } diff --git a/src/main/java/io/reactivex/Observable.java b/src/main/java/io/reactivex/Observable.java index fe2989bae4..43f1f14483 100644 --- a/src/main/java/io/reactivex/Observable.java +++ b/src/main/java/io/reactivex/Observable.java @@ -42,7 +42,7 @@ * Many operators in the class accept {@code ObservableSource}(s), the base reactive interface * for such non-backpressured flows, which {@code Observable} itself implements as well. *

- * The Observable's operators, by default, run with a buffer size of 128 elements (see {@link Flowable#bufferSize()}, + * The Observable's operators, by default, run with a buffer size of 128 elements (see {@link Flowable#bufferSize()}), * that can be overridden globally via the system parameter {@code rx2.buffer-size}. Most operators, however, have * overloads that allow setting their internal buffer size explicitly. *

@@ -51,7 +51,7 @@ * *

* The design of this class was derived from the - * Reactive-Streams design and specification + * Reactive Streams design and specification * by removing any backpressure-related infrastructure and implementation detail, replacing the * {@code org.reactivestreams.Subscription} with {@link Disposable} as the primary means to dispose of * a flow. @@ -84,12 +84,12 @@ * System.out.println("Done!"); * } * }); - * + * * Thread.sleep(500); * // the sequence can now be disposed via dispose() * d.dispose(); * - * + * * @param * the type of the items emitted by the Observable * @see Flowable @@ -1278,7 +1278,7 @@ public static Observable concat( public static Observable concatArray(ObservableSource... sources) { if (sources.length == 0) { return empty(); - } else + } if (sources.length == 1) { return wrap((ObservableSource)sources[0]); } @@ -1305,7 +1305,7 @@ public static Observable concatArray(ObservableSource... sou public static Observable concatArrayDelayError(ObservableSource... sources) { if (sources.length == 0) { return empty(); - } else + } if (sources.length == 1) { return (Observable)wrap(sources[0]); } @@ -1738,7 +1738,7 @@ public static Observable error(Callable errorSupplie @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable error(final Throwable exception) { - ObjectHelper.requireNonNull(exception, "e is null"); + ObjectHelper.requireNonNull(exception, "exception is null"); return error(Functions.justCallable(exception)); } @@ -1765,7 +1765,7 @@ public static Observable fromArray(T... items) { ObjectHelper.requireNonNull(items, "items is null"); if (items.length == 0) { return empty(); - } else + } if (items.length == 1) { return just(items[0]); } @@ -1985,12 +1985,12 @@ public static Observable fromIterable(Iterable source) { } /** - * Converts an arbitrary Reactive-Streams Publisher into an Observable. + * Converts an arbitrary Reactive Streams Publisher into an Observable. *

* *

* The {@link Publisher} must follow the - * Reactive-Streams specification. + * Reactive Streams specification. * Violating the specification may result in undefined behavior. *

* If possible, use {@link #create(ObservableOnSubscribe)} to create a @@ -2046,7 +2046,7 @@ public static Observable fromPublisher(Publisher publisher) @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable generate(final Consumer> generator) { - ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); return generate(Functions.nullSupplier(), ObservableInternalHelper.simpleGenerator(generator), Functions.emptyConsumer()); } @@ -2078,7 +2078,7 @@ public static Observable generate(final Consumer> generator) { @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable generate(Callable initialState, final BiConsumer> generator) { - ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); return generate(initialState, ObservableInternalHelper.simpleBiGenerator(generator), Functions.emptyConsumer()); } @@ -2114,7 +2114,7 @@ public static Observable generate( final Callable initialState, final BiConsumer> generator, Consumer disposeState) { - ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); return generate(initialState, ObservableInternalHelper.simpleBiGenerator(generator), disposeState); } @@ -2180,7 +2180,7 @@ public static Observable generate(Callable initialState, BiFunction public static Observable generate(Callable initialState, BiFunction, S> generator, Consumer disposeState) { ObjectHelper.requireNonNull(initialState, "initialState is null"); - ObjectHelper.requireNonNull(generator, "generator is null"); + ObjectHelper.requireNonNull(generator, "generator is null"); ObjectHelper.requireNonNull(disposeState, "disposeState is null"); return RxJavaPlugins.onAssembly(new ObservableGenerate(initialState, generator, disposeState)); } @@ -2386,7 +2386,7 @@ public static Observable intervalRange(long start, long count, long initia @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item) { - ObjectHelper.requireNonNull(item, "The item is null"); + ObjectHelper.requireNonNull(item, "item is null"); return RxJavaPlugins.onAssembly(new ObservableJust(item)); } @@ -2413,8 +2413,8 @@ public static Observable just(T item) { @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item1, T item2) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); return fromArray(item1, item2); } @@ -2444,9 +2444,9 @@ public static Observable just(T item1, T item2) { @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item1, T item2, T item3) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); return fromArray(item1, item2, item3); } @@ -2478,10 +2478,10 @@ public static Observable just(T item1, T item2, T item3) { @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item1, T item2, T item3, T item4) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); return fromArray(item1, item2, item3, item4); } @@ -2515,11 +2515,11 @@ public static Observable just(T item1, T item2, T item3, T item4) { @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item1, T item2, T item3, T item4, T item5) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); return fromArray(item1, item2, item3, item4, item5); } @@ -2555,12 +2555,12 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); return fromArray(item1, item2, item3, item4, item5, item6); } @@ -2598,13 +2598,13 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7); } @@ -2644,14 +2644,14 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8); } @@ -2693,15 +2693,15 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); - ObjectHelper.requireNonNull(item9, "The ninth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9); } @@ -2745,16 +2745,16 @@ public static Observable just(T item1, T item2, T item3, T item4, T item5 @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Observable just(T item1, T item2, T item3, T item4, T item5, T item6, T item7, T item8, T item9, T item10) { - ObjectHelper.requireNonNull(item1, "The first item is null"); - ObjectHelper.requireNonNull(item2, "The second item is null"); - ObjectHelper.requireNonNull(item3, "The third item is null"); - ObjectHelper.requireNonNull(item4, "The fourth item is null"); - ObjectHelper.requireNonNull(item5, "The fifth item is null"); - ObjectHelper.requireNonNull(item6, "The sixth item is null"); - ObjectHelper.requireNonNull(item7, "The seventh item is null"); - ObjectHelper.requireNonNull(item8, "The eighth item is null"); - ObjectHelper.requireNonNull(item9, "The ninth item is null"); - ObjectHelper.requireNonNull(item10, "The tenth item is null"); + ObjectHelper.requireNonNull(item1, "item1 is null"); + ObjectHelper.requireNonNull(item2, "item2 is null"); + ObjectHelper.requireNonNull(item3, "item3 is null"); + ObjectHelper.requireNonNull(item4, "item4 is null"); + ObjectHelper.requireNonNull(item5, "item5 is null"); + ObjectHelper.requireNonNull(item6, "item6 is null"); + ObjectHelper.requireNonNull(item7, "item7 is null"); + ObjectHelper.requireNonNull(item8, "item8 is null"); + ObjectHelper.requireNonNull(item9, "item9 is null"); + ObjectHelper.requireNonNull(item10, "item10 is null"); return fromArray(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10); } @@ -3982,7 +3982,7 @@ public static Observable timer(long delay, TimeUnit unit, Scheduler schedu /** * Create an Observable by wrapping an ObservableSource which has to be implemented according - * to the Reactive-Streams-based Observable specification by handling + * to the Reactive Streams based Observable specification by handling * disposal correctly; no safeguards are provided by the Observable itself. *

*
Scheduler:
@@ -3995,7 +3995,6 @@ public static Observable timer(long delay, TimeUnit unit, Scheduler schedu @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public static Observable unsafeCreate(ObservableSource onSubscribe) { - ObjectHelper.requireNonNull(onSubscribe, "source is null"); ObjectHelper.requireNonNull(onSubscribe, "onSubscribe is null"); if (onSubscribe instanceof Observable) { throw new IllegalArgumentException("unsafeCreate(Observable) should be upgraded"); @@ -7284,7 +7283,6 @@ public final Single contains(final Object element) { * @return a Single that emits a single item: the number of items emitted by the source ObservableSource as a * 64-bit Long item * @see ReactiveX operators documentation: Count - * @see #count() */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -8157,7 +8155,7 @@ private Observable doOnEach(Consumer onNext, Consumer doOnEach(final Consumer> onNotification) { - ObjectHelper.requireNonNull(onNotification, "consumer is null"); + ObjectHelper.requireNonNull(onNotification, "onNotification is null"); return doOnEach( Functions.notificationOnNext(onNotification), Functions.notificationOnError(onNotification), @@ -9627,7 +9625,7 @@ public final Single lastOrError() { * Example: *

 * // Step 1: Create the consumer type that will be returned by the ObservableOperator.apply():
- * 
+ *
 * public final class CustomObserver<T> implements Observer<T>, Disposable {
 *
 * // The downstream's Observer that will receive the onXXX events
@@ -9754,7 +9752,7 @@ public final Single lastOrError() {
 @CheckReturnValue
 @SchedulerSupport(SchedulerSupport.NONE)
 public final  Observable lift(ObservableOperator lifter) {
- ObjectHelper.requireNonNull(lifter, "onLift is null");
+ ObjectHelper.requireNonNull(lifter, "lifter is null");
 return RxJavaPlugins.onAssembly(new ObservableLift(this, lifter));
 }
 
@@ -9903,6 +9901,11 @@ public final Observable mergeWith(@NonNull CompletableSource other) {
 * asynchronous. If strict event ordering is required, consider using the {@link #observeOn(Scheduler, boolean)} overload.
 * 

* + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. *

*
Scheduler:
*
You specify which {@link Scheduler} this operator will use.
@@ -9919,6 +9922,7 @@ public final Observable mergeWith(@NonNull CompletableSource other) { * @see #subscribeOn * @see #observeOn(Scheduler, boolean) * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) @@ -9931,6 +9935,11 @@ public final Observable observeOn(Scheduler scheduler) { * asynchronously with an unbounded buffer with {@link Flowable#bufferSize()} "island size" and optionally delays onError notifications. *

* + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. *

*
Scheduler:
*
You specify which {@link Scheduler} this operator will use.
@@ -9951,6 +9960,7 @@ public final Observable observeOn(Scheduler scheduler) { * @see #subscribeOn * @see #observeOn(Scheduler) * @see #observeOn(Scheduler, boolean, int) + * @see #delay(long, TimeUnit, Scheduler, boolean) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) @@ -9963,6 +9973,11 @@ public final Observable observeOn(Scheduler scheduler, boolean delayError) { * asynchronously with an unbounded buffer of configurable "island size" and optionally delays onError notifications. *

* + *

+ * This operator keeps emitting as many signals as it can on the given Scheduler's Worker thread, + * which may result in a longer than expected occupation of this thread. In other terms, + * it does not allow per-signal fairness in case the worker runs on a shared underlying thread. + * If such fairness and signal/work interleaving is preferred, use the delay operator with zero time instead. *

*
Scheduler:
*
You specify which {@link Scheduler} this operator will use.
@@ -9984,6 +9999,7 @@ public final Observable observeOn(Scheduler scheduler, boolean delayError) { * @see #subscribeOn * @see #observeOn(Scheduler) * @see #observeOn(Scheduler, boolean) + * @see #delay(long, TimeUnit, Scheduler, boolean) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) @@ -11076,7 +11092,7 @@ public final Observable retry(BiPredicate *
* * @param times - * number of retry attempts before failing + * the number of times to resubscribe if the current Observable fails * @return the source ObservableSource modified with retry logic * @see ReactiveX operators documentation: Retry */ @@ -11094,7 +11110,7 @@ public final Observable retry(long times) { *
Scheduler:
*
{@code retry} does not operate by default on a particular {@link Scheduler}.
*
- * @param times the number of times to repeat + * @param times the number of times to resubscribe if the current Observable fails * @param predicate the predicate called with the failure Throwable and should return true to trigger a retry. * @return the new Observable instance */ @@ -11230,7 +11246,7 @@ public final Observable retryWhen( * Subscribes to the current Observable and wraps the given Observer into a SafeObserver * (if not already a SafeObserver) that * deals with exceptions thrown by a misbehaving Observer (that doesn't follow the - * Reactive-Streams specification). + * Reactive Streams specification). *
*
Scheduler:
*
{@code safeSubscribe} does not operate by default on a particular {@link Scheduler}.
@@ -11240,7 +11256,7 @@ public final Observable retryWhen( */ @SchedulerSupport(SchedulerSupport.NONE) public final void safeSubscribe(Observer observer) { - ObjectHelper.requireNonNull(observer, "s is null"); + ObjectHelper.requireNonNull(observer, "observer is null"); if (observer instanceof SafeObserver) { subscribe(observer); } else { @@ -11499,7 +11515,7 @@ public final Observable scan(BiFunction accumulator) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Observable scan(final R initialValue, BiFunction accumulator) { - ObjectHelper.requireNonNull(initialValue, "seed is null"); + ObjectHelper.requireNonNull(initialValue, "initialValue is null"); return scanWith(Functions.justCallable(initialValue), accumulator); } @@ -12388,6 +12404,7 @@ public final Observable switchIfEmpty(ObservableSource other) { * ObservableSource * @return an Observable that emits the items emitted by the ObservableSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource * @see ReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -12417,6 +12434,7 @@ public final Observable switchMap(FunctionReactiveX operators documentation: FlatMap + * @see #switchMapDelayError(Function, int) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -12507,7 +12525,7 @@ public final Completable switchMapCompletable(@NonNull Function Observable switchMapMaybeDelayError(@NonNull FunctionReactiveX operators documentation: FlatMap + * @see #switchMapSingleDelayError(Function) * @since 2.2 */ @CheckReturnValue @@ -12630,6 +12649,7 @@ public final Observable switchMapSingle(@NonNull FunctionReactiveX operators documentation: FlatMap + * @see #switchMapSingle(Function) * @since 2.2 */ @CheckReturnValue @@ -12661,6 +12681,7 @@ public final Observable switchMapSingleDelayError(@NonNull FunctionReactiveX operators documentation: FlatMap + * @see #switchMap(Function) * @since 2.0 */ @CheckReturnValue @@ -12692,6 +12713,7 @@ public final Observable switchMapDelayError(FunctionReactiveX operators documentation: FlatMap + * @see #switchMap(Function, int) * @since 2.0 */ @CheckReturnValue @@ -12817,10 +12839,10 @@ public final Observable take(long time, TimeUnit unit, Scheduler scheduler) { public final Observable takeLast(int count) { if (count < 0) { throw new IndexOutOfBoundsException("count>= 0 required but it was " + count); - } else + } if (count == 0) { return RxJavaPlugins.onAssembly(new ObservableIgnoreElements(this)); - } else + } if (count == 1) { return RxJavaPlugins.onAssembly(new ObservableTakeLastOne(this)); } @@ -13122,7 +13144,7 @@ public final Observable takeUntil(ObservableSource other) { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Observable takeUntil(Predicate stopPredicate) { - ObjectHelper.requireNonNull(stopPredicate, "predicate is null"); + ObjectHelper.requireNonNull(stopPredicate, "stopPredicate is null"); return RxJavaPlugins.onAssembly(new ObservableTakeUntilPredicate(this, stopPredicate)); } diff --git a/src/main/java/io/reactivex/Scheduler.java b/src/main/java/io/reactivex/Scheduler.java index 2e99e288f1..882fdaa060 100644 --- a/src/main/java/io/reactivex/Scheduler.java +++ b/src/main/java/io/reactivex/Scheduler.java @@ -61,8 +61,9 @@ * interface which can grant access to the original or hooked {@code Runnable}, thus, a repeated {@code RxJavaPlugins.onSchedule} * can detect the earlier hook and not apply a new one over again. *

- * The default implementation of {@link #now(TimeUnit)} and {@link Worker#now(TimeUnit)} methods to return current - * {@link System#currentTimeMillis()} value in the desired time unit. Custom {@code Scheduler} implementations can override this + * The default implementation of {@link #now(TimeUnit)} and {@link Worker#now(TimeUnit)} methods to return current {@link System#currentTimeMillis()} + * value in the desired time unit, unless {@code rx2.scheduler.use-nanotime} (boolean) is set. When the property is set to + * {@code true}, the method uses {@link System#nanoTime()} as its basis instead. Custom {@code Scheduler} implementations can override this * to provide specialized time accounting (such as virtual time to be advanced programmatically). * Note that operators requiring a {@code Scheduler} may rely on either of the {@code now()} calls provided by * {@code Scheduler} or {@code Worker} respectively, therefore, it is recommended they represent a logically @@ -89,6 +90,34 @@ * All methods on the {@code Scheduler} and {@code Worker} classes should be thread safe. */ public abstract class Scheduler { + /** + * Value representing whether to use {@link System#nanoTime()}, or default as clock for {@link #now(TimeUnit)} + * and {@link Scheduler.Worker#now(TimeUnit)} + *

+ * Associated system parameter: + *

    + *
  • {@code rx2.scheduler.use-nanotime}, boolean, default {@code false} + *
+ */ + static boolean IS_DRIFT_USE_NANOTIME = Boolean.getBoolean("rx2.scheduler.use-nanotime"); + + /** + * Returns the current clock time depending on state of {@link Scheduler#IS_DRIFT_USE_NANOTIME} in given {@code unit} + *

+ * By default {@link System#currentTimeMillis()} will be used as the clock. When the property is set + * {@link System#nanoTime()} will be used. + *

+ * @param unit the time unit + * @return the 'current time' in given unit + * @throws NullPointerException if {@code unit} is {@code null} + */ + static long computeNow(TimeUnit unit) { + if(!IS_DRIFT_USE_NANOTIME) { + return unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } + return unit.convert(System.nanoTime(), TimeUnit.NANOSECONDS); + } + /** * The tolerance for a clock drift in nanoseconds where the periodic scheduler will rebase. *

@@ -131,7 +160,7 @@ public static long clockDriftTolerance() { * @since 2.0 */ public long now(@NonNull TimeUnit unit) { - return unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS); + return computeNow(unit); } /** @@ -332,8 +361,9 @@ public S when(@NonNull Function - * The default implementation of the {@link #now(TimeUnit)} method returns current - * {@link System#currentTimeMillis()} value in the desired time unit. Custom {@code Worker} implementations can override this + * The default implementation of the {@link #now(TimeUnit)} method returns current {@link System#currentTimeMillis()} + * value in the desired time unit, unless {@code rx2.scheduler.use-nanotime} (boolean) is set. When the property is set to + * {@code true}, the method uses {@link System#nanoTime()} as its basis instead. Custom {@code Worker} implementations can override this * to provide specialized time accounting (such as virtual time to be advanced programmatically). * Note that operators requiring a scheduler may rely on either of the {@code now()} calls provided by * {@code Scheduler} or {@code Worker} respectively, therefore, it is recommended they represent a logically @@ -448,7 +478,7 @@ public Disposable schedulePeriodically(@NonNull Runnable run, final long initial * @since 2.0 */ public long now(@NonNull TimeUnit unit) { - return unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS); + return computeNow(unit); } /** diff --git a/src/main/java/io/reactivex/Single.java b/src/main/java/io/reactivex/Single.java index d4b5f7aaf4..15ef6b6c14 100644 --- a/src/main/java/io/reactivex/Single.java +++ b/src/main/java/io/reactivex/Single.java @@ -400,6 +400,8 @@ public static Flowable concatArray(SingleSource... sources) /** * Concatenates a sequence of SingleSource eagerly into a single stream of values. *

+ * + *

* Eager concatenation means that once a subscriber subscribes, this operator subscribes to all of the * source SingleSources. The operator buffers the value emitted by these SingleSources and then drains them * in order, each one after the previous one completes. @@ -476,7 +478,7 @@ public static Flowable concatEager(Iterable * *

@@ -584,7 +586,7 @@ public static Single error(final Callable errorSuppl @NonNull @SchedulerSupport(SchedulerSupport.NONE) public static Single error(final Throwable exception) { - ObjectHelper.requireNonNull(exception, "error is null"); + ObjectHelper.requireNonNull(exception, "exception is null"); return error(Functions.justCallable(exception)); } @@ -755,7 +757,7 @@ public static Single fromFuture(Future future, Scheduler sch * the source has more than one element, an IndexOutOfBoundsException is signalled. *

* The {@link Publisher} must follow the - * Reactive-Streams specification. + * Reactive Streams specification. * Violating the specification may result in undefined behavior. *

* If possible, use {@link #create(SingleOnSubscribe)} to create a @@ -834,7 +836,7 @@ public static Single fromObservable(ObservableSource observa @SchedulerSupport(SchedulerSupport.NONE) @NonNull public static Single just(final T item) { - ObjectHelper.requireNonNull(item, "value is null"); + ObjectHelper.requireNonNull(item, "item is null"); return RxJavaPlugins.onAssembly(new SingleJust(item)); } @@ -1108,6 +1110,8 @@ public static Flowable merge( /** * Merges an Iterable sequence of SingleSource instances into a single Flowable sequence, * running all SingleSources at once and delaying any error(s) until all sources succeed or fail. + *

+ * *

*
Backpressure:
*
The returned {@code Flowable} honors the backpressure of the downstream consumer.
@@ -1132,6 +1136,8 @@ public static Flowable mergeDelayError(Iterable + * *
*
Backpressure:
*
The returned {@code Flowable} honors the backpressure of the downstream consumer.
@@ -1159,7 +1165,7 @@ public static Flowable mergeDelayError(Publisher - * + * *

* You can combine items emitted by multiple Singles so that they appear as a single Flowable, by * using the {@code mergeDelayError} method. @@ -1197,7 +1203,7 @@ public static Flowable mergeDelayError( * Flattens three Singles into a single Flowable, without any transformation, delaying * any error(s) until all sources succeed or fail. *

- * + * *

* You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using * the {@code mergeDelayError} method. @@ -1239,7 +1245,7 @@ public static Flowable mergeDelayError( * Flattens four Singles into a single Flowable, without any transformation, delaying * any error(s) until all sources succeed or fail. *

- * + * *

* You can combine items emitted by multiple Singles so that they appear as a single Flowable, by using * the {@code mergeDelayError} method. @@ -1370,6 +1376,8 @@ public static Single equals(final SingleSource first, /** * Advanced use only: creates a Single instance without * any safeguards by using a callback that is called with a SingleObserver. + *

+ * *

*
Scheduler:
*
{@code unsafeCreate} does not operate by default on a particular {@link Scheduler}.
@@ -1396,6 +1404,8 @@ public static Single unsafeCreate(SingleSource onSubscribe) { /** * Allows using and disposing a resource while running a SingleSource instance generated from * that resource (similar to a try-with-resources). + *

+ * *

*
Scheduler:
*
{@code using} does not operate by default on a particular {@link Scheduler}.
@@ -1423,6 +1433,8 @@ public static Single using(Callable resourceSupplier, /** * Allows using and disposing a resource while running a SingleSource instance generated from * that resource (similar to a try-with-resources). + *

+ * *

*
Scheduler:
*
{@code using} does not operate by default on a particular {@link Scheduler}.
@@ -1460,6 +1472,8 @@ public static Single using( /** * Wraps a SingleSource instance into a new Single instance if not already a Single * instance. + *

+ * *

*
Scheduler:
*
{@code wrap} does not operate by default on a particular {@link Scheduler}.
@@ -2067,6 +2081,7 @@ public final Single compose(SingleTransformer tra /** * Stores the success value or exception from the current Single and replays it to late SingleObservers. *

+ * * The returned Single subscribes to the current Single when the first SingleObserver subscribes. *

*
Scheduler:
@@ -2085,6 +2100,8 @@ public final Single cache() { /** * 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}.
@@ -2225,6 +2242,8 @@ public final Single delay(final long time, final TimeUnit unit, final Schedul /** * Delays the actual subscription to the current Single until the given other CompletableSource * completes. + *

+ * *

If the delaying source signals an error, that error is re-emitted and no subscription * to the current Single happens. *

@@ -2247,6 +2266,8 @@ public final Single delaySubscription(CompletableSource other) { /** * Delays the actual subscription to the current Single until the given other SingleSource * signals success. + *

+ * *

If the delaying source signals an error, that error is re-emitted and no subscription * to the current Single happens. *

@@ -2270,6 +2291,8 @@ public final Single delaySubscription(SingleSource other) { /** * Delays the actual subscription to the current Single until the given other ObservableSource * signals its first value or completes. + *

+ * *

If the delaying source signals an error, that error is re-emitted and no subscription * to the current Single happens. *

@@ -2293,6 +2316,8 @@ public final Single delaySubscription(ObservableSource other) { /** * Delays the actual subscription to the current Single until the given other Publisher * signals its first value or completes. + *

+ * *

If the delaying source signals an error, that error is re-emitted and no subscription * to the current Single happens. *

The other source is consumed in an unbounded manner (requesting Long.MAX_VALUE from it). @@ -2320,6 +2345,8 @@ public final Single delaySubscription(Publisher other) { /** * Delays the actual subscription to the current Single until the given time delay elapsed. + *

+ * *

*
Scheduler:
*
{@code delaySubscription} does by default subscribe to the current Single @@ -2338,6 +2365,8 @@ public final Single delaySubscription(long time, TimeUnit unit) { /** * Delays the actual subscription to the current Single until the given time delay elapsed. + *

+ * *

*
Scheduler:
*
{@code delaySubscription} does by default subscribe to the current Single @@ -2360,6 +2389,8 @@ public final Single delaySubscription(long time, TimeUnit unit, Scheduler sch * {@code onSuccess}, {@code onError} or {@code onComplete} signals as a * {@link Maybe} source. *

+ * + *

* The intended use of the {@code selector} function is to perform a * type-safe identity mapping (see example) on a source that is already of type * {@code Notification}. The Java language doesn't allow @@ -2413,7 +2444,7 @@ public final Maybe dematerialize(Function> sel @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single doAfterSuccess(Consumer onAfterSuccess) { - ObjectHelper.requireNonNull(onAfterSuccess, "doAfterSuccess is null"); + ObjectHelper.requireNonNull(onAfterSuccess, "onAfterSuccess is null"); return RxJavaPlugins.onAssembly(new SingleDoAfterSuccess(this, onAfterSuccess)); } @@ -2501,13 +2532,13 @@ public final Single doOnSubscribe(final Consumer onSubscr *

* *

- * This differs from {@code doAfterTerminate} in that this happens before the {@code onComplete} or + * This differs from {@code doAfterTerminate} in that this happens before the {@code onSuccess} or * {@code onError} notification. *

*
Scheduler:
*
{@code doOnTerminate} does not operate by default on a particular {@link Scheduler}.
*
- * @param onTerminate the action to invoke when the consumer calls {@code onComplete} or {@code onError} + * @param onTerminate the action to invoke when the consumer calls {@code onSuccess} or {@code onError} * @return the new Single instance * @see ReactiveX operators documentation: Do * @see #doOnTerminate(Action) @@ -2547,6 +2578,8 @@ public final Single doOnSuccess(final Consumer onSuccess) { /** * Calls the shared consumer with the error sent via onError or the value * via onSuccess for each SingleObserver that subscribes to the current Single. + *

+ * *

*
Scheduler:
*
{@code doOnEvent} does not operate by default on a particular {@link Scheduler}.
@@ -2817,6 +2850,8 @@ public final Completable flatMapCompletable(final Function + * *
*
Scheduler:
*
{@code blockingGet} does not operate by default on a particular {@link Scheduler}.
@@ -2844,6 +2879,8 @@ public final T blockingGet() { * and providing a new {@code SingleObserver}, containing the custom operator's intended business logic, that will be * used in the subscription process going further upstream. *

+ * + *

* Generally, such a new {@code SingleObserver} will wrap the downstream's {@code SingleObserver} and forwards the * {@code onSuccess} and {@code onError} events from the upstream directly or according to the * emission pattern the custom operator's business logic requires. In addition, such operator can intercept the @@ -2980,7 +3017,7 @@ public final T blockingGet() { @NonNull @SchedulerSupport(SchedulerSupport.NONE) public final Single lift(final SingleOperator lift) { - ObjectHelper.requireNonNull(lift, "onLift is null"); + ObjectHelper.requireNonNull(lift, "lift is null"); return RxJavaPlugins.onAssembly(new SingleLift(this, lift)); } @@ -3031,6 +3068,8 @@ public final Single> materialize() { /** * Signals true if the current Single signals a success value that is Object-equals with the value * provided. + *

+ * *

*
Scheduler:
*
{@code contains} does not operate by default on a particular {@link Scheduler}.
@@ -3048,6 +3087,8 @@ public final Single contains(Object value) { /** * Signals true if the current Single signals a success value that is equal with * the value provided by calling a bi-predicate. + *

+ * *

*
Scheduler:
*
{@code contains} does not operate by default on a particular {@link Scheduler}.
@@ -3249,6 +3290,8 @@ public final Single onErrorResumeNext( /** * Nulls out references to the upstream producer and downstream SingleObserver if * the sequence is terminated or downstream calls dispose(). + *

+ * *

*
Scheduler:
*
{@code onTerminateDetach} does not operate by default on a particular {@link Scheduler}.
@@ -3356,6 +3399,8 @@ public final Flowable repeatUntil(BooleanSupplier stop) { /** * Repeatedly re-subscribes to the current Single indefinitely if it fails with an onError. + *

+ * *

*
Scheduler:
*
{@code retry} does not operate by default on a particular {@link Scheduler}.
@@ -3372,6 +3417,8 @@ public final Single retry() { /** * Repeatedly re-subscribe at most the specified times to the current Single * if it fails with an onError. + *

+ * *

*
Scheduler:
*
{@code retry} does not operate by default on a particular {@link Scheduler}.
@@ -3389,6 +3436,8 @@ public final Single retry(long times) { /** * Re-subscribe to the current Single if the given predicate returns true when the Single fails * with an onError. + *

+ * *

*
Scheduler:
*
{@code retry} does not operate by default on a particular {@link Scheduler}.
@@ -3407,6 +3456,8 @@ public final Single retry(BiPredicate pre /** * Repeatedly re-subscribe at most times or until the predicate returns false, whichever happens first * if it fails with an onError. + *

+ * *

*
Scheduler:
*
{@code retry} does not operate by default on a particular {@link Scheduler}.
@@ -3427,6 +3478,8 @@ public final Single retry(long times, Predicate predicate) /** * Re-subscribe to the current Single if the given predicate returns true when the Single fails * with an onError. + *

+ * *

*
Scheduler:
*
{@code retry} does not operate by default on a particular {@link Scheduler}.
@@ -3446,6 +3499,8 @@ public final Single retry(Predicate predicate) { * Re-subscribes to the current Single if and when the Publisher returned by the handler * function signals a value. *

+ * + *

* If the Publisher signals an onComplete, the resulting Single will signal a NoSuchElementException. *

* Note that the inner {@code Publisher} returned by the handler function should signal @@ -3492,6 +3547,8 @@ public final Single retryWhen(Function, ? extends /** * Subscribes to a Single but ignore its emission or notification. *

+ * + *

* If the Single emits an error, it is wrapped into an * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaPlugins.onError handler. @@ -3511,6 +3568,8 @@ public final Disposable subscribe() { /** * Subscribes to a Single and provides a composite callback to handle the item it emits * or any error notification it issues. + *

+ * *

*
Scheduler:
*
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
@@ -3538,6 +3597,8 @@ public final Disposable subscribe(final BiConsumer /** * Subscribes to a Single and provides a callback to handle the item it emits. *

+ * + *

* If the Single emits an error, it is wrapped into an * {@link io.reactivex.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaPlugins.onError handler. @@ -3562,6 +3623,8 @@ public final Disposable subscribe(Consumer onSuccess) { /** * Subscribes to a Single and provides callbacks to handle the item it emits or any error notification it * issues. + *

+ * *

*
Scheduler:
*
{@code subscribe} does not operate by default on a particular {@link Scheduler}.
@@ -3593,7 +3656,7 @@ public final Disposable subscribe(final Consumer onSuccess, final Con @SchedulerSupport(SchedulerSupport.NONE) @Override public final void subscribe(SingleObserver observer) { - ObjectHelper.requireNonNull(observer, "subscriber is null"); + ObjectHelper.requireNonNull(observer, "observer is null"); observer = RxJavaPlugins.onSubscribe(this, observer); @@ -3623,6 +3686,8 @@ public final void subscribe(SingleObserver observer) { /** * Subscribes a given SingleObserver (subclass) to this Single and returns the given * SingleObserver as is. + *

+ * *

Usage example: *


 * Single<Integer> source = Single.just(1);
@@ -3680,7 +3745,7 @@ public final Single subscribeOn(final Scheduler scheduler) {
 * termination of {@code other}, this will emit a {@link CancellationException} rather than go to
 * {@link SingleObserver#onSuccess(Object)}.
 * 

- * + * *

*
Scheduler:
*
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
@@ -3705,7 +3770,7 @@ public final Single takeUntil(final CompletableSource other) { * emission of an item from {@code other}, this will emit a {@link CancellationException} rather than go to * {@link SingleObserver#onSuccess(Object)}. *

- * + * *

*
Backpressure:
*
The {@code other} publisher is consumed in an unbounded fashion but will be @@ -3737,7 +3802,7 @@ public final Single takeUntil(final Publisher other) { * emission of an item from {@code other}, this will emit a {@link CancellationException} rather than go to * {@link SingleObserver#onSuccess(Object)}. *

- * + * *

*
Scheduler:
*
{@code takeUntil} does not operate by default on a particular {@link Scheduler}.
@@ -3761,6 +3826,8 @@ public final Single takeUntil(final SingleSource other) { /** * Signals a TimeoutException if the current Single doesn't signal a success value within the * specified timeout window. + *

+ * *

*
Scheduler:
*
{@code timeout} signals the TimeoutException on the {@code computation} {@link Scheduler}.
@@ -3779,6 +3846,8 @@ public final Single timeout(long timeout, TimeUnit unit) { /** * Signals a TimeoutException if the current Single doesn't signal a success value within the * specified timeout window. + *

+ * *

*
Scheduler:
*
{@code timeout} signals the TimeoutException on the {@link Scheduler} you specify.
@@ -3799,6 +3868,8 @@ public final Single timeout(long timeout, TimeUnit unit, Scheduler scheduler) /** * Runs the current Single and if it doesn't signal within the specified timeout window, it is * disposed and the other SingleSource subscribed to. + *

+ * *

*
Scheduler:
*
{@code timeout} subscribes to the other SingleSource on the {@link Scheduler} you specify.
@@ -3821,6 +3892,8 @@ public final Single timeout(long timeout, TimeUnit unit, Scheduler scheduler, /** * Runs the current Single and if it doesn't signal within the specified timeout window, it is * disposed and the other SingleSource subscribed to. + *

+ * *

*
Scheduler:
*
{@code timeout} subscribes to the other SingleSource on @@ -4005,6 +4078,8 @@ public final Observable toObservable() { /** * Returns a Single which makes sure when a SingleObserver disposes the Disposable, * that call is propagated up on the specified scheduler. + *

+ * *

*
Scheduler:
*
{@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.
@@ -4058,6 +4133,8 @@ public final Single zipWith(SingleSource other, BiFunction + * *
*
Scheduler:
*
{@code test} does not operate by default on a particular {@link Scheduler}.
@@ -4075,6 +4152,8 @@ public final TestObserver test() { /** * Creates a TestObserver optionally in cancelled state, then subscribes it to this Single. + *

+ * *

*
Scheduler:
*
{@code test} does not operate by default on a particular {@link Scheduler}.
diff --git a/src/main/java/io/reactivex/disposables/ActionDisposable.java b/src/main/java/io/reactivex/disposables/ActionDisposable.java index 447dfe2e34..f553f8b58e 100644 --- a/src/main/java/io/reactivex/disposables/ActionDisposable.java +++ b/src/main/java/io/reactivex/disposables/ActionDisposable.java @@ -16,6 +16,9 @@ import io.reactivex.functions.Action; import io.reactivex.internal.util.ExceptionHelper; +/** + * A Disposable container that manages an Action instance. + */ final class ActionDisposable extends ReferenceDisposable { private static final long serialVersionUID = -8219729196779211169L; diff --git a/src/main/java/io/reactivex/disposables/CompositeDisposable.java b/src/main/java/io/reactivex/disposables/CompositeDisposable.java index 5bed43ec77..f7a1bf4a36 100644 --- a/src/main/java/io/reactivex/disposables/CompositeDisposable.java +++ b/src/main/java/io/reactivex/disposables/CompositeDisposable.java @@ -38,26 +38,28 @@ public CompositeDisposable() { /** * Creates a CompositeDisposables with the given array of initial elements. - * @param resources the array of Disposables to start with + * @param disposables the array of Disposables to start with + * @throws NullPointerException if {@code disposables} or any of its array items is null */ - public CompositeDisposable(@NonNull Disposable... resources) { - ObjectHelper.requireNonNull(resources, "resources is null"); - this.resources = new OpenHashSet(resources.length + 1); - for (Disposable d : resources) { - ObjectHelper.requireNonNull(d, "Disposable item is null"); + public CompositeDisposable(@NonNull Disposable... disposables) { + ObjectHelper.requireNonNull(disposables, "disposables is null"); + this.resources = new OpenHashSet(disposables.length + 1); + for (Disposable d : disposables) { + ObjectHelper.requireNonNull(d, "A Disposable in the disposables array is null"); this.resources.add(d); } } /** * Creates a CompositeDisposables with the given Iterable sequence of initial elements. - * @param resources the Iterable sequence of Disposables to start with + * @param disposables the Iterable sequence of Disposables to start with + * @throws NullPointerException if {@code disposables} or any of its items is null */ - public CompositeDisposable(@NonNull Iterable resources) { - ObjectHelper.requireNonNull(resources, "resources is null"); + public CompositeDisposable(@NonNull Iterable disposables) { + ObjectHelper.requireNonNull(disposables, "disposables is null"); this.resources = new OpenHashSet(); - for (Disposable d : resources) { - ObjectHelper.requireNonNull(d, "Disposable item is null"); + for (Disposable d : disposables) { + ObjectHelper.requireNonNull(d, "A Disposable item in the disposables sequence is null"); this.resources.add(d); } } @@ -88,12 +90,13 @@ public boolean isDisposed() { /** * Adds a disposable to this container or disposes it if the * container has been disposed. - * @param d the disposable to add, not null + * @param disposable the disposable to add, not null * @return true if successful, false if this container has been disposed + * @throws NullPointerException if {@code disposable} is null */ @Override - public boolean add(@NonNull Disposable d) { - ObjectHelper.requireNonNull(d, "d is null"); + public boolean add(@NonNull Disposable disposable) { + ObjectHelper.requireNonNull(disposable, "disposable is null"); if (!disposed) { synchronized (this) { if (!disposed) { @@ -102,40 +105,41 @@ public boolean add(@NonNull Disposable d) { set = new OpenHashSet(); resources = set; } - set.add(d); + set.add(disposable); return true; } } } - d.dispose(); + disposable.dispose(); return false; } /** * Atomically adds the given array of Disposables to the container or * disposes them all if the container has been disposed. - * @param ds the array of Disposables + * @param disposables the array of Disposables * @return true if the operation was successful, false if the container has been disposed + * @throws NullPointerException if {@code disposables} or any of its array items is null */ - public boolean addAll(@NonNull Disposable... ds) { - ObjectHelper.requireNonNull(ds, "ds is null"); + public boolean addAll(@NonNull Disposable... disposables) { + ObjectHelper.requireNonNull(disposables, "disposables is null"); if (!disposed) { synchronized (this) { if (!disposed) { OpenHashSet set = resources; if (set == null) { - set = new OpenHashSet(ds.length + 1); + set = new OpenHashSet(disposables.length + 1); resources = set; } - for (Disposable d : ds) { - ObjectHelper.requireNonNull(d, "d is null"); + for (Disposable d : disposables) { + ObjectHelper.requireNonNull(d, "A Disposable in the disposables array is null"); set.add(d); } return true; } } } - for (Disposable d : ds) { + for (Disposable d : disposables) { d.dispose(); } return false; @@ -144,13 +148,13 @@ public boolean addAll(@NonNull Disposable... ds) { /** * Removes and disposes the given disposable if it is part of this * container. - * @param d the disposable to remove and dispose, not null + * @param disposable the disposable to remove and dispose, not null * @return true if the operation was successful */ @Override - public boolean remove(@NonNull Disposable d) { - if (delete(d)) { - d.dispose(); + public boolean remove(@NonNull Disposable disposable) { + if (delete(disposable)) { + disposable.dispose(); return true; } return false; @@ -159,12 +163,13 @@ public boolean remove(@NonNull Disposable d) { /** * Removes (but does not dispose) the given disposable if it is part of this * container. - * @param d the disposable to remove, not null + * @param disposable the disposable to remove, not null * @return true if the operation was successful + * @throws NullPointerException if {@code disposable} is null */ @Override - public boolean delete(@NonNull Disposable d) { - ObjectHelper.requireNonNull(d, "Disposable item is null"); + public boolean delete(@NonNull Disposable disposable) { + ObjectHelper.requireNonNull(disposable, "disposables is null"); if (disposed) { return false; } @@ -174,7 +179,7 @@ public boolean delete(@NonNull Disposable d) { } OpenHashSet set = resources; - if (set == null || !set.remove(d)) { + if (set == null || !set.remove(disposable)) { return false; } } diff --git a/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java b/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java index 09c0361108..ff36ce1cf3 100644 --- a/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java +++ b/src/main/java/io/reactivex/exceptions/ProtocolViolationException.java @@ -14,7 +14,7 @@ package io.reactivex.exceptions; /** - * Explicitly named exception to indicate a Reactive-Streams + * Explicitly named exception to indicate a Reactive Streams * protocol violation. *

History: 2.0.6 - experimental; 2.1 - beta * @since 2.2 diff --git a/src/main/java/io/reactivex/flowables/ConnectableFlowable.java b/src/main/java/io/reactivex/flowables/ConnectableFlowable.java index 21636e67da..ef5b667bac 100644 --- a/src/main/java/io/reactivex/flowables/ConnectableFlowable.java +++ b/src/main/java/io/reactivex/flowables/ConnectableFlowable.java @@ -68,6 +68,24 @@ public final Disposable connect() { return cc.disposable; } + /** + * Apply a workaround for a race condition with the regular publish().refCount() + * so that racing subscribers and refCount won't hang. + * + * @return the ConnectableFlowable to work with + * @since 2.2.10 + */ + private ConnectableFlowable onRefCount() { + if (this instanceof FlowablePublishClassic) { + @SuppressWarnings("unchecked") + FlowablePublishClassic fp = (FlowablePublishClassic) this; + return RxJavaPlugins.onAssembly( + new FlowablePublishAlt(fp.publishSource(), fp.publishBufferSize()) + ); + } + return this; + } + /** * Returns a {@code Flowable} that stays connected to this {@code ConnectableFlowable} as long as there * is at least one subscription to this {@code ConnectableFlowable}. @@ -89,7 +107,7 @@ public final Disposable connect() { @SchedulerSupport(SchedulerSupport.NONE) @BackpressureSupport(BackpressureKind.PASS_THROUGH) public Flowable refCount() { - return RxJavaPlugins.onAssembly(new FlowableRefCount(this)); + return RxJavaPlugins.onAssembly(new FlowableRefCount(onRefCount())); } /** @@ -216,7 +234,7 @@ public final Flowable refCount(int subscriberCount, long timeout, TimeUnit un ObjectHelper.verifyPositive(subscriberCount, "subscriberCount"); ObjectHelper.requireNonNull(unit, "unit is null"); ObjectHelper.requireNonNull(scheduler, "scheduler is null"); - return RxJavaPlugins.onAssembly(new FlowableRefCount(this, subscriberCount, timeout, unit, scheduler)); + return RxJavaPlugins.onAssembly(new FlowableRefCount(onRefCount(), subscriberCount, timeout, unit, scheduler)); } /** diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java index 20d2332214..ff7b6cced5 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableAndThenCompletable.java @@ -84,7 +84,7 @@ static final class NextObserver implements CompletableObserver { final CompletableObserver downstream; - public NextObserver(AtomicReference parent, CompletableObserver downstream) { + NextObserver(AtomicReference parent, CompletableObserver downstream) { this.parent = parent; this.downstream = downstream; } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java index 3e49bf0ec6..6722722390 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromAction.java @@ -17,6 +17,7 @@ import io.reactivex.disposables.*; import io.reactivex.exceptions.Exceptions; import io.reactivex.functions.Action; +import io.reactivex.plugins.RxJavaPlugins; public final class CompletableFromAction extends Completable { @@ -36,6 +37,8 @@ protected void subscribeActual(CompletableObserver observer) { Exceptions.throwIfFatal(e); if (!d.isDisposed()) { observer.onError(e); + } else { + RxJavaPlugins.onError(e); } return; } diff --git a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java index 981e6d1f1f..3ce78a167f 100644 --- a/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java +++ b/src/main/java/io/reactivex/internal/operators/completable/CompletableFromRunnable.java @@ -18,6 +18,7 @@ import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.Exceptions; +import io.reactivex.plugins.RxJavaPlugins; public final class CompletableFromRunnable extends Completable { @@ -37,6 +38,8 @@ protected void subscribeActual(CompletableObserver observer) { Exceptions.throwIfFatal(e); if (!d.isDisposed()) { observer.onError(e); + } else { + RxJavaPlugins.onError(e); } return; } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java index af6613b224..d09af33ee0 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableIterable.java @@ -62,7 +62,7 @@ static final class BlockingFlowableIterator long produced; volatile boolean done; - Throwable error; + volatile Throwable error; BlockingFlowableIterator(int batchSize) { this.queue = new SpscArrayQueue(batchSize); @@ -75,6 +75,13 @@ static final class BlockingFlowableIterator @Override public boolean hasNext() { for (;;) { + if (isDisposed()) { + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + return false; + } boolean d = done; boolean empty = queue.isEmpty(); if (d) { @@ -90,7 +97,7 @@ public boolean hasNext() { BlockingHelper.verifyNonBlocking(); lock.lock(); try { - while (!done && queue.isEmpty()) { + while (!done && queue.isEmpty() && !isDisposed()) { condition.await(); } } catch (InterruptedException ex) { @@ -175,6 +182,7 @@ public void remove() { @Override public void dispose() { SubscriptionHelper.cancel(this); + signalConsumer(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java index 298a3f21be..235d8507dc 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecent.java @@ -42,10 +42,6 @@ public BlockingFlowableMostRecent(Flowable source, T initialValue) { public Iterator iterator() { MostRecentSubscriber mostRecentSubscriber = new MostRecentSubscriber(initialValue); - /** - * Subscribe instead of unsafeSubscribe since this is the final subscribe in the chain - * since it is for BlockingObservable. - */ source.subscribe(mostRecentSubscriber); return mostRecentSubscriber.getIterable(); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java index 4e3be8a9e5..06736603f1 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableBufferTimed.java @@ -501,13 +501,14 @@ public void onComplete() { buffer = null; } - queue.offer(b); - done = true; - if (enter()) { - QueueDrainHelper.drainMaxLoop(queue, downstream, false, this, this); + if (b != null) { + queue.offer(b); + done = true; + if (enter()) { + QueueDrainHelper.drainMaxLoop(queue, downstream, false, this, this); + } + w.dispose(); } - - w.dispose(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java index 2dc316d1e9..64df7cc122 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableConcatMap.java @@ -13,7 +13,7 @@ package io.reactivex.internal.operators.flowable; import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.*; import org.reactivestreams.*; @@ -332,7 +332,7 @@ void drain() { continue; } else { active = true; - inner.setSubscription(new WeakScalarSubscription(vr, inner)); + inner.setSubscription(new SimpleScalarSubscription(vr, inner)); } } else { @@ -349,20 +349,20 @@ void drain() { } } - static final class WeakScalarSubscription implements Subscription { + static final class SimpleScalarSubscription + extends AtomicBoolean + implements Subscription { final Subscriber downstream; final T value; - boolean once; - WeakScalarSubscription(T value, Subscriber downstream) { + SimpleScalarSubscription(T value, Subscriber downstream) { this.value = value; this.downstream = downstream; } @Override public void request(long n) { - if (n> 0 && !once) { - once = true; + if (n> 0 && compareAndSet(false, true)) { Subscriber a = downstream; a.onNext(value); a.onComplete(); @@ -520,10 +520,13 @@ void drain() { vr = supplier.call(); } catch (Throwable e) { Exceptions.throwIfFatal(e); - upstream.cancel(); errors.addThrowable(e); - downstream.onError(errors.terminate()); - return; + if (!veryEnd) { + upstream.cancel(); + downstream.onError(errors.terminate()); + return; + } + vr = null; } if (vr == null) { @@ -535,7 +538,7 @@ void drain() { continue; } else { active = true; - inner.setSubscription(new WeakScalarSubscription(vr, inner)); + inner.setSubscription(new SimpleScalarSubscription(vr, inner)); } } else { active = true; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java index 92b1c48254..143e61ec55 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableDebounce.java @@ -119,7 +119,9 @@ public void onComplete() { if (!DisposableHelper.isDisposed(d)) { @SuppressWarnings("unchecked") DebounceInnerSubscriber dis = (DebounceInnerSubscriber)d; - dis.emit(); + if (dis != null) { + dis.emit(); + } DisposableHelper.dispose(debouncer); downstream.onComplete(); } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java index c68e82bb93..a7631de8b8 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFlatMap.java @@ -322,6 +322,11 @@ public void onError(Throwable t) { } if (errs.addThrowable(t)) { done = true; + if (!delayErrors) { + for (InnerSubscriber a : subscribers.getAndSet(CANCELLED)) { + a.dispose(); + } + } drain(); } else { RxJavaPlugins.onError(t); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java index c8c6201daa..d54fb15a21 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableFromArray.java @@ -126,7 +126,7 @@ void fastPath() { } T t = arr[i]; if (t == null) { - a.onError(new NullPointerException("array element is null")); + a.onError(new NullPointerException("The element at index " + i + " is null")); return; } else { a.onNext(t); @@ -156,7 +156,7 @@ void slowPath(long r) { T t = arr[i]; if (t == null) { - a.onError(new NullPointerException("array element is null")); + a.onError(new NullPointerException("The element at index " + i + " is null")); return; } else { a.onNext(t); @@ -209,7 +209,7 @@ void fastPath() { } T t = arr[i]; if (t == null) { - a.onError(new NullPointerException("array element is null")); + a.onError(new NullPointerException("The element at index " + i + " is null")); return; } else { a.tryOnNext(t); @@ -239,7 +239,7 @@ void slowPath(long r) { T t = arr[i]; if (t == null) { - a.onError(new NullPointerException("array element is null")); + a.onError(new NullPointerException("The element at index " + i + " is null")); return; } else { if (a.tryOnNext(t)) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java index 719645afe9..99a63c7b91 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableGroupBy.java @@ -262,7 +262,7 @@ public void cancel(K key) { if (groupCount.decrementAndGet() == 0) { upstream.cancel(); - if (getAndIncrement() == 0) { + if (!outputFused && getAndIncrement() == 0) { queue.clear(); } } @@ -288,7 +288,6 @@ void drainFused() { for (;;) { if (cancelled.get()) { - q.clear(); return; } @@ -519,6 +518,7 @@ public void request(long n) { public void cancel() { if (cancelled.compareAndSet(false, true)) { parent.cancel(key); + drain(); } } @@ -569,7 +569,6 @@ void drainFused() { for (;;) { if (a != null) { if (cancelled.get()) { - q.clear(); return; } @@ -624,7 +623,7 @@ void drainNormal() { T v = q.poll(); boolean empty = v == null; - if (checkTerminated(d, empty, a, delayError)) { + if (checkTerminated(d, empty, a, delayError, e)) { return; } @@ -637,7 +636,7 @@ void drainNormal() { e++; } - if (e == r && checkTerminated(done, q.isEmpty(), a, delayError)) { + if (e == r && checkTerminated(done, q.isEmpty(), a, delayError, e)) { return; } @@ -659,9 +658,15 @@ void drainNormal() { } } - boolean checkTerminated(boolean d, boolean empty, Subscriber a, boolean delayError) { + boolean checkTerminated(boolean d, boolean empty, Subscriber a, boolean delayError, long emitted) { if (cancelled.get()) { - queue.clear(); + // make sure buffered items can get replenished + while (queue.poll() != null) { + emitted++; + } + if (emitted != 0) { + parent.upstream.request(emitted); + } return true; } @@ -710,22 +715,35 @@ public T poll() { produced++; return v; } - int p = produced; - if (p != 0) { - produced = 0; - parent.upstream.request(p); - } + tryReplenish(); return null; } @Override public boolean isEmpty() { - return queue.isEmpty(); + if (queue.isEmpty()) { + tryReplenish(); + return true; + } + return false; + } + + void tryReplenish() { + int p = produced; + if (p != 0) { + produced = 0; + parent.upstream.request(p); + } } @Override public void clear() { - queue.clear(); + // make sure buffered items can get replenished + SpscLinkedArrayQueue q = queue; + while (q.poll() != null) { + produced++; + } + tryReplenish(); } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java index 271bd9c50d..c65386bbb1 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletable.java @@ -86,7 +86,7 @@ public void onNext(T t) { @Override public void onError(Throwable ex) { - SubscriptionHelper.cancel(mainSubscription); + DisposableHelper.dispose(otherObserver); HalfSerializer.onError(downstream, ex, this, error); } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java index a32a0c92fc..1787d5fce3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybe.java @@ -143,7 +143,7 @@ public void onNext(T t) { @Override public void onError(Throwable ex) { if (error.addThrowable(ex)) { - SubscriptionHelper.cancel(mainSubscription); + DisposableHelper.dispose(otherObserver); drain(); } else { RxJavaPlugins.onError(ex); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java index 586bc07c07..486cb73f8c 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingle.java @@ -143,7 +143,7 @@ public void onNext(T t) { @Override public void onError(Throwable ex) { if (error.addThrowable(ex)) { - SubscriptionHelper.cancel(mainSubscription); + DisposableHelper.dispose(otherObserver); drain(); } else { RxJavaPlugins.onError(ex); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java index 2a7d499d1a..3431f3a50b 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableObserveOn.java @@ -154,7 +154,7 @@ public final void cancel() { upstream.cancel(); worker.dispose(); - if (getAndIncrement() == 0) { + if (!outputFused && getAndIncrement() == 0) { queue.clear(); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java index 20caa2c7e3..8cbb73ebf2 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBuffer.java @@ -150,7 +150,7 @@ public void cancel() { cancelled = true; upstream.cancel(); - if (getAndIncrement() == 0) { + if (!outputFused && getAndIncrement() == 0) { queue.clear(); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java index f60c43bc31..556e54b8c3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureError.java @@ -65,6 +65,7 @@ public void onNext(T t) { downstream.onNext(t); BackpressureHelper.produced(this, 1); } else { + upstream.cancel(); onError(new MissingBackpressureException("could not emit value due to lack of requests")); } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java index 7ab84a568b..b325d2b78d 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublish.java @@ -33,7 +33,8 @@ * manner. * @param the value type */ -public final class FlowablePublish extends ConnectableFlowable implements HasUpstreamPublisher { +public final class FlowablePublish extends ConnectableFlowable +implements HasUpstreamPublisher, FlowablePublishClassic { /** * Indicates this child has been cancelled: the state is swapped in atomically and * will prevent the dispatch() to emit (too many) values to a terminated child subscriber. @@ -77,6 +78,20 @@ public Publisher source() { return source; } + /** + * The internal buffer size of this FloawblePublish operator. + * @return The internal buffer size of this FloawblePublish operator. + */ + @Override + public int publishBufferSize() { + return bufferSize; + } + + @Override + public Publisher publishSource() { + return source; + } + @Override protected void subscribeActual(Subscriber s) { onSubscribe.subscribe(s); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishAlt.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishAlt.java new file mode 100644 index 0000000000..932e0bf003 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishAlt.java @@ -0,0 +1,484 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.internal.operators.flowable; + +import java.util.concurrent.atomic.*; + +import org.reactivestreams.*; + +import io.reactivex.FlowableSubscriber; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.ResettableConnectable; +import io.reactivex.internal.fuseable.*; +import io.reactivex.internal.queue.SpscArrayQueue; +import io.reactivex.internal.subscriptions.SubscriptionHelper; +import io.reactivex.internal.util.*; +import io.reactivex.plugins.RxJavaPlugins; + +/** + * Shares a single underlying connection to the upstream Publisher + * and multicasts events to all subscribed subscribers until the upstream + * completes or the connection is disposed. + *

+ * The difference to FlowablePublish is that when the upstream terminates, + * late subscriberss will receive that terminal event until the connection is + * disposed and the ConnectableFlowable is reset to its fresh state. + * + * @param the element type + * @since 2.2.10 + */ +public final class FlowablePublishAlt extends ConnectableFlowable +implements HasUpstreamPublisher, ResettableConnectable { + + final Publisher source; + + final int bufferSize; + + final AtomicReference> current; + + public FlowablePublishAlt(Publisher source, int bufferSize) { + this.source = source; + this.bufferSize = bufferSize; + this.current = new AtomicReference>(); + } + + @Override + public Publisher source() { + return source; + } + + /** + * The internal buffer size of this FloawblePublishAlt operator. + * @return The internal buffer size of this FloawblePublishAlt operator. + */ + public int publishBufferSize() { + return bufferSize; + } + + @Override + public void connect(Consumer connection) { + PublishConnection conn; + boolean doConnect = false; + + for (;;) { + conn = current.get(); + + if (conn == null || conn.isDisposed()) { + PublishConnection fresh = new PublishConnection(current, bufferSize); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + + doConnect = !conn.connect.get() && conn.connect.compareAndSet(false, true); + break; + } + + try { + connection.accept(conn); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + + if (doConnect) { + source.subscribe(conn); + } + } + + @Override + protected void subscribeActual(Subscriber s) { + PublishConnection conn; + + for (;;) { + conn = current.get(); + + // don't create a fresh connection if the current is disposed + if (conn == null) { + PublishConnection fresh = new PublishConnection(current, bufferSize); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + + break; + } + + InnerSubscription inner = new InnerSubscription(s, conn); + s.onSubscribe(inner); + + if (conn.add(inner)) { + if (inner.isCancelled()) { + conn.remove(inner); + } else { + conn.drain(); + } + return; + } + + Throwable ex = conn.error; + if (ex != null) { + s.onError(ex); + } else { + s.onComplete(); + } + } + + @SuppressWarnings("unchecked") + @Override + public void resetIf(Disposable connection) { + current.compareAndSet((PublishConnection)connection, null); + } + + static final class PublishConnection + extends AtomicInteger + implements FlowableSubscriber, Disposable { + + private static final long serialVersionUID = -1672047311619175801L; + + final AtomicReference> current; + + final AtomicReference upstream; + + final AtomicBoolean connect; + + final AtomicReference[]> subscribers; + + final int bufferSize; + + volatile SimpleQueue queue; + + int sourceMode; + + volatile boolean done; + Throwable error; + + int consumed; + + @SuppressWarnings("rawtypes") + static final InnerSubscription[] EMPTY = new InnerSubscription[0]; + @SuppressWarnings("rawtypes") + static final InnerSubscription[] TERMINATED = new InnerSubscription[0]; + + @SuppressWarnings("unchecked") + PublishConnection(AtomicReference> current, int bufferSize) { + this.current = current; + this.upstream = new AtomicReference(); + this.connect = new AtomicBoolean(); + this.bufferSize = bufferSize; + this.subscribers = new AtomicReference[]>(EMPTY); + } + + @SuppressWarnings("unchecked") + @Override + public void dispose() { + subscribers.getAndSet(TERMINATED); + current.compareAndSet(this, null); + SubscriptionHelper.cancel(upstream); + } + + @Override + public boolean isDisposed() { + return subscribers.get() == TERMINATED; + } + + @Override + public void onSubscribe(Subscription s) { + if (SubscriptionHelper.setOnce(this.upstream, s)) { + if (s instanceof QueueSubscription) { + @SuppressWarnings("unchecked") + QueueSubscription qs = (QueueSubscription) s; + + int m = qs.requestFusion(QueueSubscription.ANY | QueueSubscription.BOUNDARY); + if (m == QueueSubscription.SYNC) { + sourceMode = m; + queue = qs; + done = true; + drain(); + return; + } + if (m == QueueSubscription.ASYNC) { + sourceMode = m; + queue = qs; + s.request(bufferSize); + return; + } + } + + queue = new SpscArrayQueue(bufferSize); + + s.request(bufferSize); + } + } + + @Override + public void onNext(T t) { + // we expect upstream to honor backpressure requests + if (sourceMode == QueueSubscription.NONE && !queue.offer(t)) { + onError(new MissingBackpressureException("Prefetch queue is full?!")); + return; + } + // since many things can happen concurrently, we have a common dispatch + // loop to act on the current state serially + drain(); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + } else { + error = t; + done = true; + drain(); + } + } + + @Override + public void onComplete() { + done = true; + drain(); + } + + void drain() { + if (getAndIncrement() != 0) { + return; + } + + int missed = 1; + SimpleQueue queue = this.queue; + int consumed = this.consumed; + int limit = this.bufferSize - (this.bufferSize>> 2); + boolean async = this.sourceMode != QueueSubscription.SYNC; + + outer: + for (;;) { + if (queue != null) { + long minDemand = Long.MAX_VALUE; + boolean hasDemand = false; + + InnerSubscription[] innerSubscriptions = subscribers.get(); + + for (InnerSubscription inner : innerSubscriptions) { + long request = inner.get(); + if (request != Long.MIN_VALUE) { + hasDemand = true; + minDemand = Math.min(request - inner.emitted, minDemand); + } + } + + if (!hasDemand) { + minDemand = 0L; + } + + while (minDemand != 0L) { + boolean d = done; + T v; + + try { + v = queue.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.get().cancel(); + queue.clear(); + done = true; + signalError(ex); + return; + } + + boolean empty = v == null; + + if (checkTerminated(d, empty)) { + return; + } + + if (empty) { + break; + } + + for (InnerSubscription inner : innerSubscriptions) { + if (!inner.isCancelled()) { + inner.downstream.onNext(v); + inner.emitted++; + } + } + + if (async && ++consumed == limit) { + consumed = 0; + upstream.get().request(limit); + } + minDemand--; + + if (innerSubscriptions != subscribers.get()) { + continue outer; + } + } + + if (checkTerminated(done, queue.isEmpty())) { + return; + } + } + + this.consumed = consumed; + missed = addAndGet(-missed); + if (missed == 0) { + break; + } + if (queue == null) { + queue = this.queue; + } + } + } + + @SuppressWarnings("unchecked") + boolean checkTerminated(boolean isDone, boolean isEmpty) { + if (isDone && isEmpty) { + Throwable ex = error; + + if (ex != null) { + signalError(ex); + } else { + for (InnerSubscription inner : subscribers.getAndSet(TERMINATED)) { + if (!inner.isCancelled()) { + inner.downstream.onComplete(); + } + } + } + return true; + } + return false; + } + + @SuppressWarnings("unchecked") + void signalError(Throwable ex) { + for (InnerSubscription inner : subscribers.getAndSet(TERMINATED)) { + if (!inner.isCancelled()) { + inner.downstream.onError(ex); + } + } + } + + boolean add(InnerSubscription inner) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // get the current producer array + InnerSubscription[] c = subscribers.get(); + // if this subscriber-to-source reached a terminal state by receiving + // an onError or onComplete, just refuse to add the new producer + if (c == TERMINATED) { + return false; + } + // we perform a copy-on-write logic + int len = c.length; + @SuppressWarnings("unchecked") + InnerSubscription[] u = new InnerSubscription[len + 1]; + System.arraycopy(c, 0, u, 0, len); + u[len] = inner; + // try setting the subscribers array + if (subscribers.compareAndSet(c, u)) { + return true; + } + // if failed, some other operation succeeded (another add, remove or termination) + // so retry + } + } + + @SuppressWarnings("unchecked") + void remove(InnerSubscription inner) { + // the state can change so we do a CAS loop to achieve atomicity + for (;;) { + // let's read the current subscribers array + InnerSubscription[] c = subscribers.get(); + int len = c.length; + // if it is either empty or terminated, there is nothing to remove so we quit + if (len == 0) { + break; + } + // let's find the supplied producer in the array + // although this is O(n), we don't expect too many child subscribers in general + int j = -1; + for (int i = 0; i < len; i++) { + if (c[i] == inner) { + j = i; + break; + } + } + // we didn't find it so just quit + if (j < 0) { + return; + } + // we do copy-on-write logic here + InnerSubscription[] u; + // we don't create a new empty array if producer was the single inhabitant + // but rather reuse an empty array + if (len == 1) { + u = EMPTY; + } else { + // otherwise, create a new array one less in size + u = new InnerSubscription[len - 1]; + // copy elements being before the given producer + System.arraycopy(c, 0, u, 0, j); + // copy elements being after the given producer + System.arraycopy(c, j + 1, u, j, len - j - 1); + } + // try setting this new array as + if (subscribers.compareAndSet(c, u)) { + break; + } + // if we failed, it means something else happened + // (a concurrent add/remove or termination), we need to retry + } + } + } + + static final class InnerSubscription extends AtomicLong + implements Subscription { + + private static final long serialVersionUID = 2845000326761540265L; + + final Subscriber downstream; + + final PublishConnection parent; + + long emitted; + + InnerSubscription(Subscriber downstream, PublishConnection parent) { + this.downstream = downstream; + this.parent = parent; + } + + @Override + public void request(long n) { + BackpressureHelper.addCancel(this, n); + parent.drain(); + } + + @Override + public void cancel() { + if (getAndSet(Long.MIN_VALUE) != Long.MIN_VALUE) { + parent.remove(this); + parent.drain(); + } + } + + public boolean isCancelled() { + return get() == Long.MIN_VALUE; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishClassic.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishClassic.java new file mode 100644 index 0000000000..27ded26592 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowablePublishClassic.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.internal.operators.flowable; + +import org.reactivestreams.Publisher; + +/** + * Interface to mark classic publish() operators to + * indicate refCount() should replace them with the Alt + * implementation. + *

+ * Without this, hooking the connectables with an intercept + * implementation would result in the unintended lack + * or presense of the replacement by refCount(). + * + * @param the element type of the sequence + * @since 2.2.10 + */ +public interface FlowablePublishClassic { + + /** + * The upstream source of this publish operator. + * @return the upstream source of this publish operator + */ + Publisher publishSource(); + + /** + * The internal buffer size of this publish operator. + * @return the internal buffer size of this publish operator + */ + int publishBufferSize(); +} diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java index bc11aa5425..2da1306632 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableRefCount.java @@ -25,7 +25,6 @@ import io.reactivex.internal.disposables.*; import io.reactivex.internal.subscriptions.SubscriptionHelper; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; /** * Returns an observable sequence that stays connected to the source as long as @@ -49,7 +48,7 @@ public final class FlowableRefCount extends Flowable { RefConnection connection; public FlowableRefCount(ConnectableFlowable source) { - this(source, 1, 0L, TimeUnit.NANOSECONDS, Schedulers.trampoline()); + this(source, 1, 0L, TimeUnit.NANOSECONDS, null); } public FlowableRefCount(ConnectableFlowable source, int n, long timeout, TimeUnit unit, @@ -116,22 +115,42 @@ void cancel(RefConnection rc) { void terminated(RefConnection rc) { synchronized (this) { - if (connection != null && connection == rc) { - connection = null; - if (rc.timer != null) { - rc.timer.dispose(); + if (source instanceof FlowablePublishClassic) { + if (connection != null && connection == rc) { + connection = null; + clearTimer(rc); } - } - if (--rc.subscriberCount == 0) { - if (source instanceof Disposable) { - ((Disposable)source).dispose(); - } else if (source instanceof ResettableConnectable) { - ((ResettableConnectable)source).resetIf(rc.get()); + + if (--rc.subscriberCount == 0) { + reset(rc); + } + } else { + if (connection != null && connection == rc) { + clearTimer(rc); + if (--rc.subscriberCount == 0) { + connection = null; + reset(rc); + } } } } } + void clearTimer(RefConnection rc) { + if (rc.timer != null) { + rc.timer.dispose(); + rc.timer = null; + } + } + + void reset(RefConnection rc) { + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + ((ResettableConnectable)source).resetIf(rc.get()); + } + } + void timeout(RefConnection rc) { synchronized (this) { if (rc.subscriberCount == 0 && rc == connection) { diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java index 7a02482b4b..196596f1b2 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableReplay.java @@ -773,6 +773,11 @@ final void removeFirst() { } setFirst(head); + // correct the tail if all items have been removed + head = get(); + if (head.get() == null) { + tail = head; + } } /** * Arranges the given node is the new head from now on. @@ -1015,7 +1020,7 @@ void truncate() { int e = 0; for (;;) { if (next != null) { - if (size> limit) { + if (size> limit && size> 1) { // never truncate the very last item just added e++; size--; prev = next; diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java index 55439eee69..66b9c48ec3 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSamplePublisher.java @@ -90,7 +90,7 @@ public void onError(Throwable t) { @Override public void onComplete() { SubscriptionHelper.cancel(other); - completeMain(); + completion(); } void setOther(Subscription o) { @@ -117,7 +117,7 @@ public void error(Throwable e) { public void complete() { upstream.cancel(); - completeOther(); + completion(); } void emit() { @@ -134,9 +134,7 @@ void emit() { } } - abstract void completeMain(); - - abstract void completeOther(); + abstract void completion(); abstract void run(); } @@ -178,12 +176,7 @@ static final class SampleMainNoLast extends SamplePublisherSubscriber { } @Override - void completeMain() { - downstream.onComplete(); - } - - @Override - void completeOther() { + void completion() { downstream.onComplete(); } @@ -207,16 +200,7 @@ static final class SampleMainEmitLast extends SamplePublisherSubscriber { } @Override - void completeMain() { - done = true; - if (wip.getAndIncrement() == 0) { - emit(); - downstream.onComplete(); - } - } - - @Override - void completeOther() { + void completion() { done = true; if (wip.getAndIncrement() == 0) { emit(); diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java index 9730bd38b2..caf8c235c4 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableSwitchMap.java @@ -199,7 +199,6 @@ void drain() { for (;;) { if (cancelled) { - active.lazySet(null); return; } @@ -314,7 +313,7 @@ void drain() { if (r != Long.MAX_VALUE) { requested.addAndGet(-e); } - inner.get().request(e); + inner.request(e); } } @@ -398,6 +397,7 @@ public void onError(Throwable t) { if (index == p.unique && p.error.addThrowable(t)) { if (!p.delayErrors) { p.upstream.cancel(); + p.done = true; } done = true; p.drain(); @@ -418,5 +418,11 @@ public void onComplete() { public void cancel() { SubscriptionHelper.cancel(this); } + + public void request(long n) { + if (fusionMode != QueueSubscription.SYNC) { + get().request(n); + } + } } } diff --git a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java index ae284ef27a..2db0eba8cc 100644 --- a/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java +++ b/src/main/java/io/reactivex/internal/operators/flowable/FlowableWindowTimed.java @@ -160,7 +160,6 @@ public void onError(Throwable t) { } downstream.onError(t); - dispose(); } @Override @@ -171,7 +170,6 @@ public void onComplete() { } downstream.onComplete(); - dispose(); } @Override @@ -184,22 +182,15 @@ public void cancel() { cancelled = true; } - public void dispose() { - DisposableHelper.dispose(timer); - } - @Override public void run() { - if (cancelled) { terminated = true; - dispose(); } queue.offer(NEXT); if (enter()) { drainLoop(); } - } void drainLoop() { @@ -221,13 +212,13 @@ void drainLoop() { if (d && (o == null || o == NEXT)) { window = null; q.clear(); - dispose(); Throwable err = error; if (err != null) { w.onError(err); } else { w.onComplete(); } + timer.dispose(); return; } @@ -251,8 +242,8 @@ void drainLoop() { window = null; queue.clear(); upstream.cancel(); - dispose(); a.onError(new MissingBackpressureException("Could not deliver first window due to lack of requests.")); + timer.dispose(); return; } } else { @@ -396,7 +387,7 @@ public void onNext(T t) { window = null; upstream.cancel(); downstream.onError(new MissingBackpressureException("Could not deliver window due to lack of requests")); - dispose(); + disposeTimer(); return; } } else { @@ -424,7 +415,6 @@ public void onError(Throwable t) { } downstream.onError(t); - dispose(); } @Override @@ -435,7 +425,6 @@ public void onComplete() { } downstream.onComplete(); - dispose(); } @Override @@ -448,8 +437,8 @@ public void cancel() { cancelled = true; } - public void dispose() { - DisposableHelper.dispose(timer); + public void disposeTimer() { + timer.dispose(); Worker w = worker; if (w != null) { w.dispose(); @@ -468,7 +457,7 @@ void drainLoop() { if (terminated) { upstream.cancel(); q.clear(); - dispose(); + disposeTimer(); return; } @@ -488,7 +477,7 @@ void drainLoop() { } else { w.onComplete(); } - dispose(); + disposeTimer(); return; } @@ -498,7 +487,7 @@ void drainLoop() { if (isHolder) { ConsumerIndexHolder consumerIndexHolder = (ConsumerIndexHolder) o; - if (restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { + if (!restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { w.onComplete(); count = 0; w = UnicastProcessor.create(bufferSize); @@ -515,7 +504,7 @@ void drainLoop() { queue.clear(); upstream.cancel(); a.onError(new MissingBackpressureException("Could not deliver first window due to lack of requests.")); - dispose(); + disposeTimer(); return; } } @@ -554,7 +543,7 @@ void drainLoop() { window = null; upstream.cancel(); downstream.onError(new MissingBackpressureException("Could not deliver window due to lack of requests")); - dispose(); + disposeTimer(); return; } } else { @@ -585,7 +574,6 @@ public void run() { p.queue.offer(this); } else { p.terminated = true; - p.dispose(); } if (p.enter()) { p.drainLoop(); @@ -682,7 +670,6 @@ public void onError(Throwable t) { } downstream.onError(t); - dispose(); } @Override @@ -693,7 +680,6 @@ public void onComplete() { } downstream.onComplete(); - dispose(); } @Override @@ -706,10 +692,6 @@ public void cancel() { cancelled = true; } - public void dispose() { - worker.dispose(); - } - void complete(UnicastProcessor w) { queue.offer(new SubjectWork(w, false)); if (enter()) { @@ -730,9 +712,9 @@ void drainLoop() { for (;;) { if (terminated) { upstream.cancel(); - dispose(); q.clear(); ws.clear(); + worker.dispose(); return; } @@ -756,7 +738,7 @@ void drainLoop() { } } ws.clear(); - dispose(); + worker.dispose(); return; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java index 3fdd26f9b6..24a7cb7701 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableIterable.java @@ -53,7 +53,7 @@ static final class BlockingObservableIterator final Condition condition; volatile boolean done; - Throwable error; + volatile Throwable error; BlockingObservableIterator(int batchSize) { this.queue = new SpscLinkedArrayQueue(batchSize); @@ -64,6 +64,13 @@ static final class BlockingObservableIterator @Override public boolean hasNext() { for (;;) { + if (isDisposed()) { + Throwable e = error; + if (e != null) { + throw ExceptionHelper.wrapOrThrow(e); + } + return false; + } boolean d = done; boolean empty = queue.isEmpty(); if (d) { @@ -80,7 +87,7 @@ public boolean hasNext() { BlockingHelper.verifyNonBlocking(); lock.lock(); try { - while (!done && queue.isEmpty()) { + while (!done && queue.isEmpty() && !isDisposed()) { condition.await(); } } finally { @@ -146,6 +153,7 @@ public void remove() { @Override public void dispose() { DisposableHelper.dispose(this); + signalConsumer(); } @Override diff --git a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java index 90a603f65c..04940be5e6 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java +++ b/src/main/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecent.java @@ -42,10 +42,6 @@ public BlockingObservableMostRecent(ObservableSource source, T initialValue) public Iterator iterator() { MostRecentObserver mostRecentObserver = new MostRecentObserver(initialValue); - /** - * Subscribe instead of unsafeSubscribe since this is the final subscribe in the chain - * since it is for BlockingObservable. - */ source.subscribe(mostRecentObserver); return mostRecentObserver.getIterable(); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java index 2ed4fcd93b..53068e7a91 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableAmb.java @@ -36,7 +36,7 @@ public void subscribeActual(Observer observer) { ObservableSource[] sources = this.sources; int count = 0; if (sources == null) { - sources = new Observable[8]; + sources = new ObservableSource[8]; try { for (ObservableSource p : sourcesIterable) { if (p == null) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java index 4373a321aa..589b71c5f9 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableBlockingSubscribe.java @@ -61,7 +61,7 @@ public static void subscribe(ObservableSource o, Observer observer) { ObservableSource[] sources = this.sources; int count = 0; if (sources == null) { - sources = new Observable[8]; + sources = new ObservableSource[8]; for (ObservableSource p : sourcesIterable) { if (count == sources.length) { ObservableSource[] b = new ObservableSource[count + (count>> 2)]; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java index 2c056eb70e..db8b9d4794 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableDebounce.java @@ -112,7 +112,9 @@ public void onComplete() { if (d != DisposableHelper.DISPOSED) { @SuppressWarnings("unchecked") DebounceInnerObserver dis = (DebounceInnerObserver)d; - dis.emit(); + if (dis != null) { + dis.emit(); + } DisposableHelper.dispose(debouncer); downstream.onComplete(); } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java index a4766f389d..73a5306513 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFlatMap.java @@ -334,6 +334,7 @@ void drainLoop() { if (checkTerminate()) { return; } + int innerCompleted = 0; SimplePlainQueue svq = queue; if (svq != null) { @@ -349,9 +350,18 @@ void drainLoop() { } child.onNext(o); + innerCompleted++; } } + if (innerCompleted != 0) { + if (maxConcurrency != Integer.MAX_VALUE) { + subscribeMore(innerCompleted); + innerCompleted = 0; + } + continue; + } + boolean d = done; svq = queue; InnerObserver[] inner = observers.get(); @@ -376,7 +386,6 @@ void drainLoop() { return; } - int innerCompleted = 0; if (n != 0) { long startId = lastId; int index = lastIndex; @@ -463,20 +472,12 @@ void drainLoop() { if (innerCompleted != 0) { if (maxConcurrency != Integer.MAX_VALUE) { - while (innerCompleted-- != 0) { - ObservableSource p; - synchronized (this) { - p = sources.poll(); - if (p == null) { - wip--; - continue; - } - } - subscribeInner(p); - } + subscribeMore(innerCompleted); + innerCompleted = 0; } continue; } + missed = addAndGet(-missed); if (missed == 0) { break; @@ -484,6 +485,20 @@ void drainLoop() { } } + void subscribeMore(int innerCompleted) { + while (innerCompleted-- != 0) { + ObservableSource p; + synchronized (this) { + p = sources.poll(); + if (p == null) { + wip--; + continue; + } + } + subscribeInner(p); + } + } + boolean checkTerminate() { if (cancelled) { return true; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java index 11b871ee46..9a04a4fcc3 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableFromArray.java @@ -102,7 +102,7 @@ void run() { for (int i = 0; i < n && !isDisposed(); i++) { T value = a[i]; if (value == null) { - downstream.onError(new NullPointerException("The " + i + "th element is null")); + downstream.onError(new NullPointerException("The element at index " + i + " is null")); return; } downstream.onNext(value); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java index fa020b6ae4..3b9e649062 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletable.java @@ -80,7 +80,7 @@ public void onNext(T t) { @Override public void onError(Throwable ex) { - DisposableHelper.dispose(mainDisposable); + DisposableHelper.dispose(otherObserver); HalfSerializer.onError(downstream, ex, this, error); } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java index 23b2532d9b..e7caad3b21 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybe.java @@ -106,7 +106,7 @@ public void onNext(T t) { @Override public void onError(Throwable ex) { if (error.addThrowable(ex)) { - DisposableHelper.dispose(mainDisposable); + DisposableHelper.dispose(otherObserver); drain(); } else { RxJavaPlugins.onError(ex); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java index 20c4d21b5c..7332a29a25 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingle.java @@ -106,7 +106,7 @@ public void onNext(T t) { @Override public void onError(Throwable ex) { if (error.addThrowable(ex)) { - DisposableHelper.dispose(mainDisposable); + DisposableHelper.dispose(otherObserver); drain(); } else { RxJavaPlugins.onError(ex); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java index abf1f0bb85..56de30041e 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableObserveOn.java @@ -145,7 +145,7 @@ public void dispose() { disposed = true; upstream.dispose(); worker.dispose(); - if (getAndIncrement() == 0) { + if (!outputFused && getAndIncrement() == 0) { queue.clear(); } } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java index debc37875b..04b90506c3 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublish.java @@ -30,7 +30,8 @@ * manner. * @param the value type */ -public final class ObservablePublish extends ConnectableObservable implements HasUpstreamObservableSource { +public final class ObservablePublish extends ConnectableObservable +implements HasUpstreamObservableSource, ObservablePublishClassic { /** The source observable. */ final ObservableSource source; /** Holds the current subscriber that is, will be or just was subscribed to the source observable. */ @@ -63,6 +64,11 @@ public ObservableSource source() { return source; } + @Override + public ObservableSource publishSource() { + return source; + } + @Override protected void subscribeActual(Observer observer) { onSubscribe.subscribe(observer); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishAlt.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishAlt.java new file mode 100644 index 0000000000..a9e62babde --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishAlt.java @@ -0,0 +1,282 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.internal.operators.observable; + +import java.util.concurrent.atomic.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.Exceptions; +import io.reactivex.functions.Consumer; +import io.reactivex.internal.disposables.*; +import io.reactivex.internal.fuseable.HasUpstreamObservableSource; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.observables.ConnectableObservable; + +/** + * Shares a single underlying connection to the upstream ObservableSource + * and multicasts events to all subscribed observers until the upstream + * completes or the connection is disposed. + *

+ * The difference to ObservablePublish is that when the upstream terminates, + * late observers will receive that terminal event until the connection is + * disposed and the ConnectableObservable is reset to its fresh state. + * + * @param the element type + * @since 2.2.10 + */ +public final class ObservablePublishAlt extends ConnectableObservable +implements HasUpstreamObservableSource, ResettableConnectable { + + final ObservableSource source; + + final AtomicReference> current; + + public ObservablePublishAlt(ObservableSource source) { + this.source = source; + this.current = new AtomicReference>(); + } + + @Override + public void connect(Consumer connection) { + boolean doConnect = false; + PublishConnection conn; + + for (;;) { + conn = current.get(); + + if (conn == null || conn.isDisposed()) { + PublishConnection fresh = new PublishConnection(current); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + + doConnect = !conn.connect.get() && conn.connect.compareAndSet(false, true); + break; + } + + try { + connection.accept(conn); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + throw ExceptionHelper.wrapOrThrow(ex); + } + + if (doConnect) { + source.subscribe(conn); + } + } + + @Override + protected void subscribeActual(Observer observer) { + PublishConnection conn; + + for (;;) { + conn = current.get(); + // we don't create a fresh connection if the current is terminated + if (conn == null) { + PublishConnection fresh = new PublishConnection(current); + if (!current.compareAndSet(conn, fresh)) { + continue; + } + conn = fresh; + } + break; + } + + InnerDisposable inner = new InnerDisposable(observer, conn); + observer.onSubscribe(inner); + if (conn.add(inner)) { + if (inner.isDisposed()) { + conn.remove(inner); + } + return; + } + // Late observers will be simply terminated + Throwable error = conn.error; + if (error != null) { + observer.onError(error); + } else { + observer.onComplete(); + } + } + + @Override + @SuppressWarnings("unchecked") + public void resetIf(Disposable connection) { + current.compareAndSet((PublishConnection)connection, null); + } + + @Override + public ObservableSource source() { + return source; + } + + static final class PublishConnection + extends AtomicReference[]> + implements Observer, Disposable { + + private static final long serialVersionUID = -3251430252873581268L; + + final AtomicBoolean connect; + + final AtomicReference> current; + + final AtomicReference upstream; + + @SuppressWarnings("rawtypes") + static final InnerDisposable[] EMPTY = new InnerDisposable[0]; + + @SuppressWarnings("rawtypes") + static final InnerDisposable[] TERMINATED = new InnerDisposable[0]; + + Throwable error; + + @SuppressWarnings("unchecked") + PublishConnection(AtomicReference> current) { + this.connect = new AtomicBoolean(); + this.current = current; + this.upstream = new AtomicReference(); + lazySet(EMPTY); + } + + @SuppressWarnings("unchecked") + @Override + public void dispose() { + getAndSet(TERMINATED); + current.compareAndSet(this, null); + DisposableHelper.dispose(upstream); + } + + @Override + public boolean isDisposed() { + return get() == TERMINATED; + } + + @Override + public void onSubscribe(Disposable d) { + DisposableHelper.setOnce(upstream, d); + } + + @Override + public void onNext(T t) { + for (InnerDisposable inner : get()) { + inner.downstream.onNext(t); + } + } + + @Override + @SuppressWarnings("unchecked") + public void onError(Throwable e) { + error = e; + upstream.lazySet(DisposableHelper.DISPOSED); + for (InnerDisposable inner : getAndSet(TERMINATED)) { + inner.downstream.onError(e); + } + } + + @Override + @SuppressWarnings("unchecked") + public void onComplete() { + upstream.lazySet(DisposableHelper.DISPOSED); + for (InnerDisposable inner : getAndSet(TERMINATED)) { + inner.downstream.onComplete(); + } + } + + public boolean add(InnerDisposable inner) { + for (;;) { + InnerDisposable[] a = get(); + if (a == TERMINATED) { + return false; + } + int n = a.length; + @SuppressWarnings("unchecked") + InnerDisposable[] b = new InnerDisposable[n + 1]; + System.arraycopy(a, 0, b, 0, n); + b[n] = inner; + if (compareAndSet(a, b)) { + return true; + } + } + } + + @SuppressWarnings("unchecked") + public void remove(InnerDisposable inner) { + for (;;) { + InnerDisposable[] a = get(); + int n = a.length; + if (n == 0) { + return; + } + + int j = -1; + for (int i = 0; i < n; i++) { + if (a[i] == inner) { + j = i; + break; + } + } + + if (j < 0) { + return; + } + InnerDisposable[] b = EMPTY; + if (n != 1) { + b = new InnerDisposable[n - 1]; + System.arraycopy(a, 0, b, 0, j); + System.arraycopy(a, j + 1, b, j, n - j - 1); + } + if (compareAndSet(a, b)) { + return; + } + } + } + } + + /** + * Intercepts the dispose signal from the downstream and + * removes itself from the connection's observers array + * at most once. + * @param the element type + */ + static final class InnerDisposable + extends AtomicReference> + implements Disposable { + + private static final long serialVersionUID = 7463222674719692880L; + + final Observer downstream; + + InnerDisposable(Observer downstream, PublishConnection parent) { + this.downstream = downstream; + lazySet(parent); + } + + @Override + public void dispose() { + PublishConnection p = getAndSet(null); + if (p != null) { + p.remove(this); + } + } + + @Override + public boolean isDisposed() { + return get() == null; + } + } +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishClassic.java b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishClassic.java new file mode 100644 index 0000000000..b2bfe87f71 --- /dev/null +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservablePublishClassic.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.internal.operators.observable; + +import io.reactivex.ObservableSource; + +/** + * Interface to mark classic publish() operators to + * indicate refCount() should replace them with the Alt + * implementation. + *

+ * Without this, hooking the connectables with an intercept + * implementation would result in the unintended lack + * or presense of the replacement by refCount(). + * + * @param the element type of the sequence + * @since 2.2.10 + */ +public interface ObservablePublishClassic { + + /** + * The upstream source of this publish operator. + * @return the upstream source of this publish operator + */ + ObservableSource publishSource(); +} diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java index 5abc174350..27e633c664 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableRefCount.java @@ -22,7 +22,6 @@ import io.reactivex.internal.disposables.*; import io.reactivex.observables.ConnectableObservable; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; /** * Returns an observable sequence that stays connected to the source as long as @@ -46,7 +45,7 @@ public final class ObservableRefCount extends Observable { RefConnection connection; public ObservableRefCount(ConnectableObservable source) { - this(source, 1, 0L, TimeUnit.NANOSECONDS, Schedulers.trampoline()); + this(source, 1, 0L, TimeUnit.NANOSECONDS, null); } public ObservableRefCount(ConnectableObservable source, int n, long timeout, TimeUnit unit, @@ -113,22 +112,42 @@ void cancel(RefConnection rc) { void terminated(RefConnection rc) { synchronized (this) { - if (connection != null && connection == rc) { - connection = null; - if (rc.timer != null) { - rc.timer.dispose(); + if (source instanceof ObservablePublishClassic) { + if (connection != null && connection == rc) { + connection = null; + clearTimer(rc); } - } - if (--rc.subscriberCount == 0) { - if (source instanceof Disposable) { - ((Disposable)source).dispose(); - } else if (source instanceof ResettableConnectable) { - ((ResettableConnectable)source).resetIf(rc.get()); + + if (--rc.subscriberCount == 0) { + reset(rc); + } + } else { + if (connection != null && connection == rc) { + clearTimer(rc); + if (--rc.subscriberCount == 0) { + connection = null; + reset(rc); + } } } } } + void clearTimer(RefConnection rc) { + if (rc.timer != null) { + rc.timer.dispose(); + rc.timer = null; + } + } + + void reset(RefConnection rc) { + if (source instanceof Disposable) { + ((Disposable)source).dispose(); + } else if (source instanceof ResettableConnectable) { + ((ResettableConnectable)source).resetIf(rc.get()); + } + } + void timeout(RefConnection rc) { synchronized (this) { if (rc.subscriberCount == 0 && rc == connection) { diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java index 197ada9706..2818d975f1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableReplay.java @@ -638,6 +638,11 @@ final void trimHead() { } setFirst(head); + // correct the tail if all items have been removed + head = get(); + if (head.get() == null) { + tail = head; + } } /** * Arranges the given node is the new head from now on. @@ -839,7 +844,7 @@ void truncate() { int e = 0; for (;;) { if (next != null) { - if (size> limit) { + if (size> limit && size> 1) { // never truncate the very last item just added e++; size--; prev = next; diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java index fcb0f33a00..1d5f8a5fe1 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSampleWithObservable.java @@ -84,7 +84,7 @@ public void onError(Throwable t) { @Override public void onComplete() { DisposableHelper.dispose(other); - completeMain(); + completion(); } boolean setOther(Disposable o) { @@ -109,7 +109,7 @@ public void error(Throwable e) { public void complete() { upstream.dispose(); - completeOther(); + completion(); } void emit() { @@ -119,9 +119,7 @@ void emit() { } } - abstract void completeMain(); - - abstract void completeOther(); + abstract void completion(); abstract void run(); } @@ -163,12 +161,7 @@ static final class SampleMainNoLast extends SampleMainObserver { } @Override - void completeMain() { - downstream.onComplete(); - } - - @Override - void completeOther() { + void completion() { downstream.onComplete(); } @@ -192,16 +185,7 @@ static final class SampleMainEmitLast extends SampleMainObserver { } @Override - void completeMain() { - done = true; - if (wip.getAndIncrement() == 0) { - emit(); - downstream.onComplete(); - } - } - - @Override - void completeOther() { + void completion() { done = true; if (wip.getAndIncrement() == 0) { emit(); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java index 8c5aa371dc..4e97ea405d 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableSwitchMap.java @@ -314,6 +314,7 @@ void innerError(SwitchMapInnerObserver inner, Throwable ex) { if (inner.index == unique && errors.addThrowable(ex)) { if (!delayErrors) { upstream.dispose(); + done = true; } inner.done = true; drain(); diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java index 7bd29092b4..213588032b 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimed.java @@ -139,6 +139,7 @@ void drain() { final Observer a = downstream; final SpscLinkedArrayQueue q = queue; final boolean delayError = this.delayError; + final long timestampLimit = scheduler.now(unit) - time; for (;;) { if (cancelled) { @@ -171,7 +172,7 @@ void drain() { @SuppressWarnings("unchecked") T o = (T)q.poll(); - if ((Long)ts < scheduler.now(unit) - time) { + if ((Long)ts < timestampLimit) { continue; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java index 1ffc0f475e..5214d2b225 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableWindowTimed.java @@ -15,14 +15,13 @@ import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.Scheduler.Worker; import io.reactivex.disposables.Disposable; -import io.reactivex.internal.disposables.DisposableHelper; +import io.reactivex.internal.disposables.*; import io.reactivex.internal.observers.QueueDrainObserver; import io.reactivex.internal.queue.MpscLinkedQueue; import io.reactivex.internal.util.NotificationLite; @@ -85,7 +84,7 @@ static final class WindowExactUnboundedObserver UnicastSubject window; - final AtomicReference timer = new AtomicReference(); + final SequentialDisposable timer = new SequentialDisposable(); static final Object NEXT = new Object(); @@ -114,7 +113,7 @@ public void onSubscribe(Disposable d) { if (!cancelled) { Disposable task = scheduler.schedulePeriodicallyDirect(this, timespan, timespan, unit); - DisposableHelper.replace(timer, task); + timer.replace(task); } } } @@ -146,7 +145,6 @@ public void onError(Throwable t) { drainLoop(); } - disposeTimer(); downstream.onError(t); } @@ -157,7 +155,6 @@ public void onComplete() { drainLoop(); } - disposeTimer(); downstream.onComplete(); } @@ -171,15 +168,10 @@ public boolean isDisposed() { return cancelled; } - void disposeTimer() { - DisposableHelper.dispose(timer); - } - @Override public void run() { if (cancelled) { terminated = true; - disposeTimer(); } queue.offer(NEXT); if (enter()) { @@ -206,13 +198,13 @@ void drainLoop() { if (d && (o == null || o == NEXT)) { window = null; q.clear(); - disposeTimer(); Throwable err = error; if (err != null) { w.onError(err); } else { w.onComplete(); } + timer.dispose(); return; } @@ -266,7 +258,7 @@ static final class WindowExactBoundedObserver volatile boolean terminated; - final AtomicReference timer = new AtomicReference(); + final SequentialDisposable timer = new SequentialDisposable(); WindowExactBoundedObserver( Observer> actual, @@ -312,7 +304,7 @@ public void onSubscribe(Disposable d) { task = scheduler.schedulePeriodicallyDirect(consumerIndexHolder, timespan, timespan, unit); } - DisposableHelper.replace(timer, task); + timer.replace(task); } } @@ -370,7 +362,6 @@ public void onError(Throwable t) { } downstream.onError(t); - disposeTimer(); } @Override @@ -381,7 +372,6 @@ public void onComplete() { } downstream.onComplete(); - disposeTimer(); } @Override @@ -428,13 +418,13 @@ void drainLoop() { if (d && (empty || isHolder)) { window = null; q.clear(); - disposeTimer(); Throwable err = error; if (err != null) { w.onError(err); } else { w.onComplete(); } + disposeTimer(); return; } @@ -444,7 +434,7 @@ void drainLoop() { if (isHolder) { ConsumerIndexHolder consumerIndexHolder = (ConsumerIndexHolder) o; - if (restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { + if (!restartTimerOnMaxSize || producerIndex == consumerIndexHolder.index) { w.onComplete(); count = 0; w = UnicastSubject.create(bufferSize); @@ -507,7 +497,6 @@ public void run() { p.queue.offer(this); } else { p.terminated = true; - p.disposeTimer(); } if (p.enter()) { p.drainLoop(); @@ -592,7 +581,6 @@ public void onError(Throwable t) { } downstream.onError(t); - disposeWorker(); } @Override @@ -603,7 +591,6 @@ public void onComplete() { } downstream.onComplete(); - disposeWorker(); } @Override @@ -616,10 +603,6 @@ public boolean isDisposed() { return cancelled; } - void disposeWorker() { - worker.dispose(); - } - void complete(UnicastSubject w) { queue.offer(new SubjectWork(w, false)); if (enter()) { @@ -640,9 +623,9 @@ void drainLoop() { for (;;) { if (terminated) { upstream.dispose(); - disposeWorker(); q.clear(); ws.clear(); + worker.dispose(); return; } @@ -665,8 +648,8 @@ void drainLoop() { w.onComplete(); } } - disposeWorker(); ws.clear(); + worker.dispose(); return; } diff --git a/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java b/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java index a259cd6d56..7f00383834 100644 --- a/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java +++ b/src/main/java/io/reactivex/internal/operators/observable/ObservableZip.java @@ -50,7 +50,7 @@ public void subscribeActual(Observer observer) { ObservableSource[] sources = this.sources; int count = 0; if (sources == null) { - sources = new Observable[8]; + sources = new ObservableSource[8]; for (ObservableSource p : sourcesIterable) { if (count == sources.length) { ObservableSource[] b = new ObservableSource[count + (count>> 2)]; @@ -179,6 +179,7 @@ public void drain() { if (z.done && !delayError) { Throwable ex = z.error; if (ex != null) { + cancelled = true; cancel(); a.onError(ex); return; @@ -224,6 +225,7 @@ boolean checkTerminated(boolean d, boolean empty, Observer a, boolean if (delayError) { if (empty) { Throwable e = source.error; + cancelled = true; cancel(); if (e != null) { a.onError(e); @@ -235,11 +237,13 @@ boolean checkTerminated(boolean d, boolean empty, Observer a, boolean } else { Throwable e = source.error; if (e != null) { + cancelled = true; cancel(); a.onError(e); return true; } else if (empty) { + cancelled = true; cancel(); a.onComplete(); return true; diff --git a/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java b/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java index d806321bf3..02cb44a1d3 100644 --- a/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java +++ b/src/main/java/io/reactivex/internal/schedulers/IoScheduler.java @@ -48,6 +48,10 @@ public final class IoScheduler extends Scheduler { /** The name of the system property for setting the thread priority for this Scheduler. */ private static final String KEY_IO_PRIORITY = "rx2.io-priority"; + /** The name of the system property for setting the release behaviour for this Scheduler. */ + private static final String KEY_SCHEDULED_RELEASE = "rx2.io-scheduled-release"; + static boolean USE_SCHEDULED_RELEASE; + static final CachedWorkerPool NONE; static { @@ -63,6 +67,8 @@ public final class IoScheduler extends Scheduler { EVICTOR_THREAD_FACTORY = new RxThreadFactory(EVICTOR_THREAD_NAME_PREFIX, priority); + USE_SCHEDULED_RELEASE = Boolean.getBoolean(KEY_SCHEDULED_RELEASE); + NONE = new CachedWorkerPool(0, null, WORKER_THREAD_FACTORY); NONE.shutdown(); } @@ -200,7 +206,7 @@ public int size() { return pool.get().allWorkers.size(); } - static final class EventLoopWorker extends Scheduler.Worker { + static final class EventLoopWorker extends Scheduler.Worker implements Runnable { private final CompositeDisposable tasks; private final CachedWorkerPool pool; private final ThreadWorker threadWorker; @@ -217,12 +223,20 @@ static final class EventLoopWorker extends Scheduler.Worker { public void dispose() { if (once.compareAndSet(false, true)) { tasks.dispose(); - - // releasing the pool should be the last action - pool.release(threadWorker); + if (USE_SCHEDULED_RELEASE) { + threadWorker.scheduleActual(this, 0, TimeUnit.NANOSECONDS, null); + } else { + // releasing the pool should be the last action + pool.release(threadWorker); + } } } + @Override + public void run() { + pool.release(threadWorker); + } + @Override public boolean isDisposed() { return once.get(); diff --git a/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java b/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java index b0b1339d29..ab465046aa 100644 --- a/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java +++ b/src/main/java/io/reactivex/internal/schedulers/SchedulerPoolFactory.java @@ -20,6 +20,8 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; +import io.reactivex.functions.Function; + /** * Manages the creating of ScheduledExecutorServices and sets up purging. */ @@ -90,40 +92,48 @@ public static void shutdown() { } static { - Properties properties = System.getProperties(); - - PurgeProperties pp = new PurgeProperties(); - pp.load(properties); - - PURGE_ENABLED = pp.purgeEnable; - PURGE_PERIOD_SECONDS = pp.purgePeriod; + SystemPropertyAccessor propertyAccessor = new SystemPropertyAccessor(); + PURGE_ENABLED = getBooleanProperty(true, PURGE_ENABLED_KEY, true, true, propertyAccessor); + PURGE_PERIOD_SECONDS = getIntProperty(PURGE_ENABLED, PURGE_PERIOD_SECONDS_KEY, 1, 1, propertyAccessor); start(); } - static final class PurgeProperties { - - boolean purgeEnable; - - int purgePeriod; - - void load(Properties properties) { - if (properties.containsKey(PURGE_ENABLED_KEY)) { - purgeEnable = Boolean.parseBoolean(properties.getProperty(PURGE_ENABLED_KEY)); - } else { - purgeEnable = true; + static int getIntProperty(boolean enabled, String key, int defaultNotFound, int defaultNotEnabled, Function propertyAccessor) { + if (enabled) { + try { + String value = propertyAccessor.apply(key); + if (value == null) { + return defaultNotFound; + } + return Integer.parseInt(value); + } catch (Throwable ex) { + return defaultNotFound; } + } + return defaultNotEnabled; + } - if (purgeEnable && properties.containsKey(PURGE_PERIOD_SECONDS_KEY)) { - try { - purgePeriod = Integer.parseInt(properties.getProperty(PURGE_PERIOD_SECONDS_KEY)); - } catch (NumberFormatException ex) { - purgePeriod = 1; + static boolean getBooleanProperty(boolean enabled, String key, boolean defaultNotFound, boolean defaultNotEnabled, Function propertyAccessor) { + if (enabled) { + try { + String value = propertyAccessor.apply(key); + if (value == null) { + return defaultNotFound; } - } else { - purgePeriod = 1; + return "true".equals(value); + } catch (Throwable ex) { + return defaultNotFound; } } + return defaultNotEnabled; + } + + static final class SystemPropertyAccessor implements Function { + @Override + public String apply(String t) throws Exception { + return System.getProperty(t); + } } /** diff --git a/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java b/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java index 0fb9d5666c..c7770bf163 100644 --- a/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java +++ b/src/main/java/io/reactivex/internal/subscribers/StrictSubscriber.java @@ -23,7 +23,7 @@ /** * Ensures that the event flow between the upstream and downstream follow - * the Reactive-Streams 1.0 specification by honoring the 3 additional rules + * the Reactive Streams 1.0 specification by honoring the 3 additional rules * (which are omitted in standard operators due to performance reasons). *

    *
  • §1.3: onNext should not be called concurrently until onSubscribe returns
  • diff --git a/src/main/java/io/reactivex/observables/ConnectableObservable.java b/src/main/java/io/reactivex/observables/ConnectableObservable.java index b5e54054b1..09fa70899e 100644 --- a/src/main/java/io/reactivex/observables/ConnectableObservable.java +++ b/src/main/java/io/reactivex/observables/ConnectableObservable.java @@ -66,6 +66,23 @@ public final Disposable connect() { return cc.disposable; } + /** + * Apply a workaround for a race condition with the regular publish().refCount() + * so that racing observers and refCount won't hang. + * + * @return the ConnectableObservable to work with + * @since 2.2.10 + */ + @SuppressWarnings("unchecked") + private ConnectableObservable onRefCount() { + if (this instanceof ObservablePublishClassic) { + return RxJavaPlugins.onAssembly( + new ObservablePublishAlt(((ObservablePublishClassic)this).publishSource()) + ); + } + return this; + } + /** * Returns an {@code Observable} that stays connected to this {@code ConnectableObservable} as long as there * is at least one subscription to this {@code ConnectableObservable}. @@ -83,7 +100,7 @@ public final Disposable connect() { @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public Observable refCount() { - return RxJavaPlugins.onAssembly(new ObservableRefCount(this)); + return RxJavaPlugins.onAssembly(new ObservableRefCount(onRefCount())); } /** @@ -190,7 +207,7 @@ public final Observable refCount(int subscriberCount, long timeout, TimeUnit ObjectHelper.verifyPositive(subscriberCount, "subscriberCount"); ObjectHelper.requireNonNull(unit, "unit is null"); ObjectHelper.requireNonNull(scheduler, "scheduler is null"); - return RxJavaPlugins.onAssembly(new ObservableRefCount(this, subscriberCount, timeout, unit, scheduler)); + return RxJavaPlugins.onAssembly(new ObservableRefCount(onRefCount(), subscriberCount, timeout, unit, scheduler)); } /** diff --git a/src/main/java/io/reactivex/observers/BaseTestConsumer.java b/src/main/java/io/reactivex/observers/BaseTestConsumer.java index 61db5a2356..4c92c27468 100644 --- a/src/main/java/io/reactivex/observers/BaseTestConsumer.java +++ b/src/main/java/io/reactivex/observers/BaseTestConsumer.java @@ -555,7 +555,6 @@ public final U assertValues(T... values) { * @return this * @since 2.2 */ - @SuppressWarnings("unchecked") public final U assertValuesOnly(T... values) { return assertSubscribed() .assertValues(values) diff --git a/src/main/java/io/reactivex/package-info.java b/src/main/java/io/reactivex/package-info.java index 7d78294b6d..75ceb6cd7b 100644 --- a/src/main/java/io/reactivex/package-info.java +++ b/src/main/java/io/reactivex/package-info.java @@ -25,7 +25,7 @@ * Completable/CompletableObserver interfaces and associated operators (in * the {@code io.reactivex.internal.operators} package) are inspired by the * Reactive Rx library in Microsoft .NET but designed and implemented on - * the more advanced Reactive-Streams ( http://www.reactivestreams.org ) principles.

    + * the more advanced Reactive Streams ( http://www.reactivestreams.org ) principles.

    *

    * More information can be found at http://msdn.microsoft.com/en-us/data/gg577609. diff --git a/src/main/java/io/reactivex/processors/MulticastProcessor.java b/src/main/java/io/reactivex/processors/MulticastProcessor.java index 317244313f..31b7a64fea 100644 --- a/src/main/java/io/reactivex/processors/MulticastProcessor.java +++ b/src/main/java/io/reactivex/processors/MulticastProcessor.java @@ -569,6 +569,7 @@ void drain() { } } + consumed = c; missed = wip.addAndGet(-missed); if (missed == 0) { break; diff --git a/src/main/java/io/reactivex/processors/ReplayProcessor.java b/src/main/java/io/reactivex/processors/ReplayProcessor.java index ff98ff25d9..b04a17d07e 100644 --- a/src/main/java/io/reactivex/processors/ReplayProcessor.java +++ b/src/main/java/io/reactivex/processors/ReplayProcessor.java @@ -1070,6 +1070,10 @@ void trim() { TimedNode h = head; for (;;) { + if (size <= 1) { + head = h; + break; + } TimedNode next = h.get(); if (next == null) { head = h; @@ -1082,6 +1086,7 @@ void trim() { } h = next; + size--; } } diff --git a/src/main/java/io/reactivex/processors/UnicastProcessor.java b/src/main/java/io/reactivex/processors/UnicastProcessor.java index 94547b88fb..ec4d6c1a77 100644 --- a/src/main/java/io/reactivex/processors/UnicastProcessor.java +++ b/src/main/java/io/reactivex/processors/UnicastProcessor.java @@ -347,7 +347,6 @@ void drainFused(Subscriber a) { for (;;) { if (cancelled) { - q.clear(); downstream.lazySet(null); return; } @@ -550,10 +549,11 @@ public void cancel() { doTerminate(); - if (!enableOperatorFusion) { - if (wip.getAndIncrement() == 0) { + downstream.lazySet(null); + if (wip.getAndIncrement() == 0) { + downstream.lazySet(null); + if (!enableOperatorFusion) { queue.clear(); - downstream.lazySet(null); } } } diff --git a/src/main/java/io/reactivex/processors/package-info.java b/src/main/java/io/reactivex/processors/package-info.java index 64caf9a4c4..b6a619372f 100644 --- a/src/main/java/io/reactivex/processors/package-info.java +++ b/src/main/java/io/reactivex/processors/package-info.java @@ -15,7 +15,25 @@ */ /** - * Classes extending the Flowable base reactive class and implementing - * the Subscriber interface at the same time (aka hot Flowables). + * Classes representing so-called hot backpressure-aware sources, aka processors, + * that implement the {@link io.reactivex.processors.FlowableProcessor FlowableProcessor} class, + * the Reactive Streams {@link org.reactivestreams.Processor Processor} interface + * to allow forms of multicasting events to one or more subscribers as well as consuming another + * Reactive Streams {@link org.reactivestreams.Publisher Publisher}. + *

    + * Available processor implementations: + *
    + *

      + *
    • {@link io.reactivex.processors.AsyncProcessor AsyncProcessor} - replays the very last item
    • + *
    • {@link io.reactivex.processors.BehaviorProcessor BehaviorProcessor} - remembers the latest item
    • + *
    • {@link io.reactivex.processors.MulticastProcessor MulticastProcessor} - coordinates its source with its consumers
    • + *
    • {@link io.reactivex.processors.PublishProcessor PublishProcessor} - dispatches items to current consumers
    • + *
    • {@link io.reactivex.processors.ReplayProcessor ReplayProcessor} - remembers some or all items and replays them to consumers
    • + *
    • {@link io.reactivex.processors.UnicastProcessor UnicastProcessor} - remembers or relays items to a single consumer
    • + *
    + *

    + * The non-backpressured variants of the {@code FlowableProcessor} class are called + * {@link io.reactivex.subjects.Subject}s and reside in the {@code io.reactivex.subjects} package. + * @see io.reactivex.subjects */ package io.reactivex.processors; diff --git a/src/main/java/io/reactivex/schedulers/Schedulers.java b/src/main/java/io/reactivex/schedulers/Schedulers.java index 4edaf65b1a..5af187c121 100644 --- a/src/main/java/io/reactivex/schedulers/Schedulers.java +++ b/src/main/java/io/reactivex/schedulers/Schedulers.java @@ -31,12 +31,16 @@ *

      *
    • {@code rx2.io-keep-alive-time} (long): sets the keep-alive time of the {@link #io()} Scheduler workers, default is {@link IoScheduler#KEEP_ALIVE_TIME_DEFAULT}
    • *
    • {@code rx2.io-priority} (int): sets the thread priority of the {@link #io()} Scheduler, default is {@link Thread#NORM_PRIORITY}
    • + *
    • {@code rx2.io-scheduled-release} (boolean): {@code true} sets the worker release mode of the + * {@link #io()} Scheduler to scheduled, default is {@code false} for eager mode.
    • *
    • {@code rx2.computation-threads} (int): sets the number of threads in the {@link #computation()} Scheduler, default is the number of available CPUs
    • *
    • {@code rx2.computation-priority} (int): sets the thread priority of the {@link #computation()} Scheduler, default is {@link Thread#NORM_PRIORITY}
    • *
    • {@code rx2.newthread-priority} (int): sets the thread priority of the {@link #newThread()} Scheduler, default is {@link Thread#NORM_PRIORITY}
    • *
    • {@code rx2.single-priority} (int): sets the thread priority of the {@link #single()} Scheduler, default is {@link Thread#NORM_PRIORITY}
    • *
    • {@code rx2.purge-enabled} (boolean): enables periodic purging of all Scheduler's backing thread pools, default is false
    • *
    • {@code rx2.purge-period-seconds} (int): specifies the periodic purge interval of all Scheduler's backing thread pools, default is 1 second
    • + *
    • {@code rx2.scheduler.use-nanotime} (boolean): {@code true} instructs {@code Scheduler} to use {@link System#nanoTime()} for {@link Scheduler#now(TimeUnit)}, + * instead of default {@link System#currentTimeMillis()} ({@code false})
    • *
    */ public final class Schedulers { @@ -113,6 +117,8 @@ private Schedulers() { *
      *
    • {@code rx2.computation-threads} (int): sets the number of threads in the {@link #computation()} Scheduler, default is the number of available CPUs
    • *
    • {@code rx2.computation-priority} (int): sets the thread priority of the {@link #computation()} Scheduler, default is {@link Thread#NORM_PRIORITY}
    • + *
    • {@code rx2.io-scheduled-release} (boolean): {@code true} sets the worker release mode of the + * {@code #io()} Scheduler to scheduled, default is {@code false} for eager mode.
    • *
    *

    * The default value of this scheduler can be overridden at initialization time via the @@ -129,6 +135,21 @@ private Schedulers() { *

    Operators on the base reactive classes that use this scheduler are marked with the * @{@link io.reactivex.annotations.SchedulerSupport SchedulerSupport}({@link io.reactivex.annotations.SchedulerSupport#COMPUTATION COMPUTATION}) * annotation. + *

    + * When the {@link Scheduler.Worker} is disposed, the underlying worker can be released to the cached worker pool in two modes: + *

      + *
    • In eager mode (default), the underlying worker is returned immediately to the cached worker pool + * and can be reused much quicker by operators. The drawback is that if the currently running task doesn't + * respond to interruption in time or at all, this may lead to delays or deadlock with the reuse use of the + * underlying worker. + *
    • + *
    • In scheduled mode (enabled via the system parameter {@code rx2.io-scheduled-release} + * set to {@code true}), the underlying worker is returned to the cached worker pool only after the currently running task + * has finished. This can help prevent premature reuse of the underlying worker and likely won't lead to delays or + * deadlock with such reuses. The drawback is that the delay in release may lead to an excess amount of underlying + * workers being created. + *
    • + *
    * @return a {@link Scheduler} meant for computation-bound work */ @NonNull @@ -299,7 +320,7 @@ public static Scheduler single() { * a time delay or periodically will use the {@link #single()} scheduler for the timed waiting * before posting the actual task to the given executor. *

    - * Tasks submitted to the {@link Scheduler.Worker} of this {@code Scheduler} are also not interruptible. Use the + * Tasks submitted to the {@link io.reactivex.Scheduler.Worker Scheduler.Worker} of this {@code Scheduler} are also not interruptible. Use the * {@link #from(Executor, boolean)} overload to enable task interruption via this wrapper. *

    * If the provided executor supports the standard Java {@link ExecutorService} API, @@ -332,7 +353,7 @@ public static Scheduler single() { * } *

*

- * This type of scheduler is less sensitive to leaking {@link Scheduler.Worker} instances, although + * This type of scheduler is less sensitive to leaking {@link io.reactivex.Scheduler.Worker Scheduler.Worker} instances, although * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or * execute those tasks "unexpectedly". *

@@ -350,7 +371,7 @@ public static Scheduler from(@NonNull Executor executor) { * Wraps an {@link Executor} into a new Scheduler instance and delegates {@code schedule()} * calls to it. *

- * The tasks scheduled by the returned {@link Scheduler} and its {@link Scheduler.Worker} + * The tasks scheduled by the returned {@link Scheduler} and its {@link io.reactivex.Scheduler.Worker Scheduler.Worker} * can be optionally interrupted. *

* If the provided executor doesn't support any of the more specific standard Java executor @@ -388,14 +409,14 @@ public static Scheduler from(@NonNull Executor executor) { * } *

*

- * This type of scheduler is less sensitive to leaking {@link Scheduler.Worker} instances, although + * This type of scheduler is less sensitive to leaking {@link io.reactivex.Scheduler.Worker Scheduler.Worker} instances, although * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or * execute those tasks "unexpectedly". *

* Note that this method returns a new {@link Scheduler} instance, even for the same {@link Executor} instance. * @param executor * the executor to wrap - * @param interruptibleWorker if {@code true} the tasks submitted to the {@link Scheduler.Worker} will + * @param interruptibleWorker if {@code true} the tasks submitted to the {@link io.reactivex.Scheduler.Worker Scheduler.Worker} will * be interrupted when the task is disposed. * @return the new Scheduler wrapping the Executor * @since 2.2.6 - experimental diff --git a/src/main/java/io/reactivex/subjects/ReplaySubject.java b/src/main/java/io/reactivex/subjects/ReplaySubject.java index 703692bd57..622854b5ec 100644 --- a/src/main/java/io/reactivex/subjects/ReplaySubject.java +++ b/src/main/java/io/reactivex/subjects/ReplaySubject.java @@ -1071,6 +1071,10 @@ void trim() { TimedNode h = head; for (;;) { + if (size <= 1) { + head = h; + break; + } TimedNode next = h.get(); if (next == null) { head = h; @@ -1083,6 +1087,7 @@ void trim() { } h = next; + size--; } } diff --git a/src/main/java/io/reactivex/subjects/UnicastSubject.java b/src/main/java/io/reactivex/subjects/UnicastSubject.java index 8c3eae8af0..20aadbd460 100644 --- a/src/main/java/io/reactivex/subjects/UnicastSubject.java +++ b/src/main/java/io/reactivex/subjects/UnicastSubject.java @@ -420,7 +420,6 @@ void drainFused(Observer a) { if (disposed) { downstream.lazySet(null); - q.clear(); return; } boolean d = done; @@ -558,7 +557,9 @@ public void dispose() { downstream.lazySet(null); if (wip.getAndIncrement() == 0) { downstream.lazySet(null); - queue.clear(); + if (!enableOperatorFusion) { + queue.clear(); + } } } } diff --git a/src/main/java/io/reactivex/subjects/package-info.java b/src/main/java/io/reactivex/subjects/package-info.java index 8bd3b06ac2..091c223445 100644 --- a/src/main/java/io/reactivex/subjects/package-info.java +++ b/src/main/java/io/reactivex/subjects/package-info.java @@ -29,7 +29,7 @@ *
{@link io.reactivex.subjects.BehaviorSubject BehaviorSubject} *
{@link io.reactivex.subjects.PublishSubject PublishSubject} *
{@link io.reactivex.subjects.ReplaySubject ReplaySubject} - *
{@link io.reactivex.subjects.UnicastSubject UnicastSubjectSubject} + *
{@link io.reactivex.subjects.UnicastSubject UnicastSubject} * * {@link io.reactivex.Observable Observable} * {@link io.reactivex.Observer Observer} diff --git a/src/main/resources/META-INF/proguard/rxjava2.pro b/src/main/resources/META-INF/proguard/rxjava2.pro new file mode 100644 index 0000000000..d51378e562 --- /dev/null +++ b/src/main/resources/META-INF/proguard/rxjava2.pro @@ -0,0 +1 @@ +-dontwarn java.util.concurrent.Flow* \ No newline at end of file diff --git a/src/test/java/io/reactivex/NotificationTest.java b/src/test/java/io/reactivex/NotificationTest.java index c3283edd3c..504c0425bc 100644 --- a/src/test/java/io/reactivex/NotificationTest.java +++ b/src/test/java/io/reactivex/NotificationTest.java @@ -41,11 +41,11 @@ public void valueOfOnCompleteIsNull() { @Test public void notEqualsToObject() { Notification n1 = Notification.createOnNext(0); - assertFalse(n1.equals(0)); + assertNotEquals(0, n1); Notification n2 = Notification.createOnError(new TestException()); - assertFalse(n2.equals(0)); + assertNotEquals(0, n2); Notification n3 = Notification.createOnComplete(); - assertFalse(n3.equals(0)); + assertNotEquals(0, n3); } @Test diff --git a/src/test/java/io/reactivex/SchedulerTest.java b/src/test/java/io/reactivex/SchedulerTest.java new file mode 100644 index 0000000000..39bcb0ae4b --- /dev/null +++ b/src/test/java/io/reactivex/SchedulerTest.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex; + +import org.junit.After; +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +/** + * Same as {@link io.reactivex.schedulers.SchedulerTest}, but different package, to access + * package-private fields. + */ +public class SchedulerTest { + private static final String DRIFT_USE_NANOTIME = "rx2.scheduler.use-nanotime"; + + @After + public void cleanup() { + // reset value to default in order to not influence other tests + Scheduler.IS_DRIFT_USE_NANOTIME = false; + } + + @Test + public void driftUseNanoTimeNotSetByDefault() { + assertFalse(Scheduler.IS_DRIFT_USE_NANOTIME); + assertFalse(Boolean.getBoolean(DRIFT_USE_NANOTIME)); + } + + @Test + public void computeNow_currentTimeMillis() { + TimeUnit unit = TimeUnit.MILLISECONDS; + assertTrue(isInRange(System.currentTimeMillis(), Scheduler.computeNow(unit), unit, 250, TimeUnit.MILLISECONDS)); + } + + @Test + public void computeNow_nanoTime() { + TimeUnit unit = TimeUnit.NANOSECONDS; + Scheduler.IS_DRIFT_USE_NANOTIME = true; + + assertFalse(isInRange(System.currentTimeMillis(), Scheduler.computeNow(unit), unit, 250, TimeUnit.MILLISECONDS)); + assertTrue(isInRange(System.nanoTime(), Scheduler.computeNow(unit), TimeUnit.NANOSECONDS, 250, TimeUnit.MILLISECONDS)); + } + + private boolean isInRange(long start, long stop, TimeUnit source, long maxDiff, TimeUnit diffUnit) { + long diff = Math.abs(stop - start); + return diffUnit.convert(diff, source) <= maxDiff; + } +} diff --git a/src/test/java/io/reactivex/TimesteppingScheduler.java b/src/test/java/io/reactivex/TimesteppingScheduler.java new file mode 100644 index 0000000000..1bf0df2b8e --- /dev/null +++ b/src/test/java/io/reactivex/TimesteppingScheduler.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex; + +import java.util.concurrent.TimeUnit; + +import io.reactivex.Scheduler; +import io.reactivex.disposables.*; + +/** + * Basic scheduler that produces an ever increasing {@link #now(TimeUnit)} value. + * Use this scheduler only as a time source! + */ +public class TimesteppingScheduler extends Scheduler { + + final class TimesteppingWorker extends Worker { + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public Disposable schedule(Runnable run, long delay, TimeUnit unit) { + run.run(); + return Disposables.disposed(); + } + + @Override + public long now(TimeUnit unit) { + return TimesteppingScheduler.this.now(unit); + } + } + + public long time; + + public boolean stepEnabled; + + @Override + public Worker createWorker() { + return new TimesteppingWorker(); + } + + @Override + public long now(TimeUnit unit) { + if (stepEnabled) { + return time++; + } + return time; + } +} \ No newline at end of file diff --git a/src/test/java/io/reactivex/completable/CompletableTest.java b/src/test/java/io/reactivex/completable/CompletableTest.java index 4798973835..40e52c8e24 100644 --- a/src/test/java/io/reactivex/completable/CompletableTest.java +++ b/src/test/java/io/reactivex/completable/CompletableTest.java @@ -14,7 +14,6 @@ package io.reactivex.completable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -2398,18 +2397,20 @@ public void retryTimes5Error() { @Test(timeout = 5000) public void retryTimes5Normal() { - final AtomicInteger calls = new AtomicInteger(5); + final AtomicInteger calls = new AtomicInteger(); Completable c = Completable.fromAction(new Action() { @Override public void run() { - if (calls.decrementAndGet() != 0) { + if (calls.incrementAndGet() != 6) { throw new TestException(); } } }).retry(5); c.blockingAwait(); + + assertEquals(6, calls.get()); } @Test(expected = IllegalArgumentException.class) @@ -4324,7 +4325,7 @@ public boolean test(Throwable t) { Assert.assertEquals(2, errors.size()); Assert.assertTrue(errors.get(0).toString(), errors.get(0) instanceof TestException); - Assert.assertEquals(errors.get(0).toString(), null, errors.get(0).getMessage()); + Assert.assertNull(errors.get(0).toString(), errors.get(0).getMessage()); Assert.assertTrue(errors.get(1).toString(), errors.get(1) instanceof TestException); Assert.assertEquals(errors.get(1).toString(), "Forced inner failure", errors.get(1).getMessage()); } diff --git a/src/test/java/io/reactivex/disposables/DisposablesTest.java b/src/test/java/io/reactivex/disposables/DisposablesTest.java index 779b85fead..531838acc1 100644 --- a/src/test/java/io/reactivex/disposables/DisposablesTest.java +++ b/src/test/java/io/reactivex/disposables/DisposablesTest.java @@ -14,7 +14,6 @@ package io.reactivex.disposables; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/exceptions/OnNextValueTest.java b/src/test/java/io/reactivex/exceptions/OnNextValueTest.java index 6e0541436b..c0d9df5981 100644 --- a/src/test/java/io/reactivex/exceptions/OnNextValueTest.java +++ b/src/test/java/io/reactivex/exceptions/OnNextValueTest.java @@ -71,7 +71,7 @@ public void onError(Throwable e) { assertTrue(trace, trace.contains("OnNextValue")); - assertTrue("No Cause on throwable" + e, e.getCause() != null); + assertNotNull("No Cause on throwable" + e, e.getCause()); // assertTrue(e.getCause().getClass().getSimpleName() + " no OnNextValue", // e.getCause() instanceof OnErrorThrowable.OnNextValue); } diff --git a/src/test/java/io/reactivex/flowable/FlowableMergeTests.java b/src/test/java/io/reactivex/flowable/FlowableMergeTests.java index 5c96bcf096..5facc6665f 100644 --- a/src/test/java/io/reactivex/flowable/FlowableMergeTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableMergeTests.java @@ -69,7 +69,7 @@ public void testMergeCovariance3() { assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); - assertTrue(values.get(2) != null); + assertNotNull(values.get(2)); assertTrue(values.get(3) instanceof HorrorMovie); } @@ -92,7 +92,7 @@ public Publisher call() { assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); - assertTrue(values.get(2) != null); + assertNotNull(values.get(2)); assertTrue(values.get(3) instanceof HorrorMovie); } diff --git a/src/test/java/io/reactivex/flowable/FlowableNotificationTest.java b/src/test/java/io/reactivex/flowable/FlowableNotificationTest.java index e068cb7399..37b79676d3 100644 --- a/src/test/java/io/reactivex/flowable/FlowableNotificationTest.java +++ b/src/test/java/io/reactivex/flowable/FlowableNotificationTest.java @@ -23,28 +23,28 @@ public class FlowableNotificationTest { public void testOnNextIntegerNotificationDoesNotEqualNullNotification() { final Notification integerNotification = Notification.createOnNext(1); final Notification nullNotification = Notification.createOnNext(null); - Assert.assertFalse(integerNotification.equals(nullNotification)); + Assert.assertNotEquals(integerNotification, nullNotification); } @Test(expected = NullPointerException.class) public void testOnNextNullNotificationDoesNotEqualIntegerNotification() { final Notification integerNotification = Notification.createOnNext(1); final Notification nullNotification = Notification.createOnNext(null); - Assert.assertFalse(nullNotification.equals(integerNotification)); + Assert.assertNotEquals(nullNotification, integerNotification); } @Test public void testOnNextIntegerNotificationsWhenEqual() { final Notification integerNotification = Notification.createOnNext(1); final Notification integerNotification2 = Notification.createOnNext(1); - Assert.assertTrue(integerNotification.equals(integerNotification2)); + Assert.assertEquals(integerNotification, integerNotification2); } @Test public void testOnNextIntegerNotificationsWhenNotEqual() { final Notification integerNotification = Notification.createOnNext(1); final Notification integerNotification2 = Notification.createOnNext(2); - Assert.assertFalse(integerNotification.equals(integerNotification2)); + Assert.assertNotEquals(integerNotification, integerNotification2); } @Test @@ -52,7 +52,7 @@ public void testOnNextIntegerNotificationsWhenNotEqual() { public void testOnErrorIntegerNotificationDoesNotEqualNullNotification() { final Notification integerNotification = Notification.createOnError(new Exception()); final Notification nullNotification = Notification.createOnError(null); - Assert.assertFalse(integerNotification.equals(nullNotification)); + Assert.assertNotEquals(integerNotification, nullNotification); } @Test @@ -60,7 +60,7 @@ public void testOnErrorIntegerNotificationDoesNotEqualNullNotification() { public void testOnErrorNullNotificationDoesNotEqualIntegerNotification() { final Notification integerNotification = Notification.createOnError(new Exception()); final Notification nullNotification = Notification.createOnError(null); - Assert.assertFalse(nullNotification.equals(integerNotification)); + Assert.assertNotEquals(nullNotification, integerNotification); } @Test @@ -68,13 +68,13 @@ public void testOnErrorIntegerNotificationsWhenEqual() { final Exception exception = new Exception(); final Notification onErrorNotification = Notification.createOnError(exception); final Notification onErrorNotification2 = Notification.createOnError(exception); - Assert.assertTrue(onErrorNotification.equals(onErrorNotification2)); + Assert.assertEquals(onErrorNotification, onErrorNotification2); } @Test public void testOnErrorIntegerNotificationWhenNotEqual() { final Notification onErrorNotification = Notification.createOnError(new Exception()); final Notification onErrorNotification2 = Notification.createOnError(new Exception()); - Assert.assertFalse(onErrorNotification.equals(onErrorNotification2)); + Assert.assertNotEquals(onErrorNotification, onErrorNotification2); } } diff --git a/src/test/java/io/reactivex/flowable/FlowableTests.java b/src/test/java/io/reactivex/flowable/FlowableTests.java index fa8026da6c..c9e226da9e 100644 --- a/src/test/java/io/reactivex/flowable/FlowableTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableTests.java @@ -14,7 +14,6 @@ package io.reactivex.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/flowable/FlowableWindowTests.java b/src/test/java/io/reactivex/flowable/FlowableWindowTests.java index 9ef4211aa4..08151bcb8b 100644 --- a/src/test/java/io/reactivex/flowable/FlowableWindowTests.java +++ b/src/test/java/io/reactivex/flowable/FlowableWindowTests.java @@ -16,11 +16,15 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.Test; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.functions.*; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.TestScheduler; +import io.reactivex.subscribers.TestSubscriber; public class FlowableWindowTests { @@ -50,4 +54,43 @@ public void accept(List xs) { assertEquals(2, lists.size()); } + + @Test + public void timeSizeWindowAlternatingBounds() { + TestScheduler scheduler = new TestScheduler(); + PublishProcessor pp = PublishProcessor.create(); + + TestSubscriber> ts = pp.window(5, TimeUnit.SECONDS, scheduler, 2) + .flatMapSingle(new Function, SingleSource>>() { + @Override + public SingleSource> apply(Flowable v) { + return v.toList(); + } + }) + .test(); + + pp.onNext(1); + pp.onNext(2); + ts.assertValueCount(1); // size bound hit + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + pp.onNext(3); + scheduler.advanceTimeBy(6, TimeUnit.SECONDS); + ts.assertValueCount(2); // time bound hit + + pp.onNext(4); + pp.onNext(5); + + ts.assertValueCount(3); // size bound hit again + + pp.onNext(4); + + scheduler.advanceTimeBy(6, TimeUnit.SECONDS); + + ts.assertValueCount(4) + .assertNoErrors() + .assertNotComplete(); + + ts.cancel(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java index b0d551a64e..412e3a2d1f 100644 --- a/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/completable/CompletableFromCallableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.completable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java index 92dcd3dd02..a0249db01e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableLatestTest.java @@ -43,13 +43,13 @@ public void testSimple() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } @Test(timeout = 1000) @@ -68,13 +68,13 @@ public void testSameSourceMultipleIterators() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } } @@ -86,7 +86,7 @@ public void testEmpty() { Iterator it = iter.iterator(); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); it.next(); } @@ -165,7 +165,7 @@ public void testFasterSource() { source.onNext(7); source.onComplete(); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } @Ignore("THe target is an enum") diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java index 26e6eb5716..70a43694d4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableMostRecentTest.java @@ -28,7 +28,7 @@ public class BlockingFlowableMostRecentTest { @Test public void testMostRecentNull() { - assertEquals(null, Flowable.never().blockingMostRecent(null).iterator().next()); + assertNull(Flowable.never().blockingMostRecent(null).iterator().next()); } @Test @@ -87,12 +87,12 @@ public void testSingleSourceManyIterators() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java index 32ee150cf3..e2e2cc4337 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableNextTest.java @@ -318,7 +318,7 @@ public void testSingleSourceManyIterators() throws InterruptedException { BlockingFlowableNext.NextIterator it = (BlockingFlowableNext.NextIterator)iter.iterator(); for (long i = 0; i < 10; i++) { - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(j + "th iteration next", Long.valueOf(i), it.next()); } terminal.onNext(1); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToFutureTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToFutureTest.java index d90b0b2f43..c1363a452b 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToFutureTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToFutureTest.java @@ -120,6 +120,6 @@ public void testGetWithEmptyFlowable() throws Throwable { public void testGetWithASingleNullItem() throws Exception { Flowable obs = Flowable.just((String)null); Future f = obs.toFuture(); - assertEquals(null, f.get()); + assertNull(f.get()); } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java index 412d59ac5f..68490f9257 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/BlockingFlowableToIteratorTest.java @@ -16,14 +16,18 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.*; import org.reactivestreams.*; import io.reactivex.Flowable; +import io.reactivex.disposables.Disposable; import io.reactivex.exceptions.*; import io.reactivex.internal.operators.flowable.BlockingFlowableIterable.BlockingFlowableIterator; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.Schedulers; public class BlockingFlowableToIteratorTest { @@ -33,16 +37,16 @@ public void testToIterator() { Iterator it = obs.blockingIterable().iterator(); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("one", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("two", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("three", it.next()); - assertEquals(false, it.hasNext()); + assertFalse(it.hasNext()); } @@ -60,10 +64,10 @@ public void subscribe(Subscriber subscriber) { Iterator it = obs.blockingIterable().iterator(); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("one", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); it.next(); } @@ -185,4 +189,28 @@ protected void subscribeActual(Subscriber s) { it.next(); } + + @Test(expected = NoSuchElementException.class) + public void disposedIteratorHasNextReturns() { + Iterator it = PublishProcessor.create() + .blockingIterable().iterator(); + ((Disposable)it).dispose(); + assertFalse(it.hasNext()); + it.next(); + } + + @Test + public void asyncDisposeUnblocks() { + final Iterator it = PublishProcessor.create() + .blockingIterable().iterator(); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + ((Disposable)it).dispose(); + } + }, 1, TimeUnit.SECONDS); + + assertFalse(it.hasNext()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java index e859766ca8..409510b23f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAllTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java index 76d4b034b7..5c8a9d6f84 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAnyTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAsObservableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAsObservableTest.java index a297911040..7dd78955ee 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableAsObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableAsObservableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import org.junit.Test; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java index 5060c58253..d0023d3e38 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableBufferTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -2769,4 +2768,20 @@ public void timedSizeBufferAlreadyCleared() { sub.run(); } + + @Test + @SuppressWarnings("unchecked") + public void bufferExactFailingSupplier() { + Flowable.empty() + .buffer(1, TimeUnit.SECONDS, Schedulers.computation(), 10, new Callable() { + @Override + public List call() throws Exception { + throw new TestException(); + } + }, false) + .test() + .awaitDone(1, TimeUnit.SECONDS) + .assertFailure(TestException.class) + ; + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java index 4443a71759..a96b8c8193 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableCombineLatestTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java index eba09e564f..8bd29121a0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatMapTest.java @@ -15,16 +15,18 @@ import static org.junit.Assert.assertEquals; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.reactivestreams.Publisher; import io.reactivex.*; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; -import io.reactivex.internal.operators.flowable.FlowableConcatMap.WeakScalarSubscription; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableConcatMap.SimpleScalarSubscription; +import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; @@ -33,7 +35,7 @@ public class FlowableConcatMapTest { @Test public void weakSubscriptionRequest() { TestSubscriber ts = new TestSubscriber(0); - WeakScalarSubscription ws = new WeakScalarSubscription(1, ts); + SimpleScalarSubscription ws = new SimpleScalarSubscription(1, ts); ts.onSubscribe(ws); ws.request(0); @@ -105,6 +107,68 @@ public Publisher apply(String v) .assertResult("RxSingleScheduler"); } + @Test + public void innerScalarRequestRace() { + final Flowable just = Flowable.just(1); + final int n = 1000; + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor> source = PublishProcessor.create(); + + final TestSubscriber ts = source + .concatMap(Functions.>identity(), n + 1) + .test(1L); + + TestHelper.race(new Runnable() { + @Override + public void run() { + for (int j = 0; j < n; j++) { + source.onNext(just); + } + } + }, new Runnable() { + @Override + public void run() { + for (int j = 0; j < n; j++) { + ts.request(1); + } + } + }); + + ts.assertValueCount(n); + } + } + + @Test + public void innerScalarRequestRaceDelayError() { + final Flowable just = Flowable.just(1); + final int n = 1000; + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + final PublishProcessor> source = PublishProcessor.create(); + + final TestSubscriber ts = source + .concatMapDelayError(Functions.>identity(), n + 1, true) + .test(1L); + + TestHelper.race(new Runnable() { + @Override + public void run() { + for (int j = 0; j < n; j++) { + source.onNext(just); + } + } + }, new Runnable() { + @Override + public void run() { + for (int j = 0; j < n; j++) { + ts.request(1); + } + } + }); + + ts.assertValueCount(n); + } + } + @Test public void pollThrows() { Flowable.just(1) @@ -168,4 +232,42 @@ public void run() throws Exception { assertEquals(0, counter.get()); } + + @Test + public void delayErrorCallableTillTheEnd() { + Flowable.just(1, 2, 3, 101, 102, 23, 890, 120, 32) + .concatMapDelayError(new Function>() { + @Override public Flowable apply(final Integer integer) throws Exception { + return Flowable.fromCallable(new Callable() { + @Override public Integer call() throws Exception { + if (integer>= 100) { + throw new NullPointerException("test null exp"); + } + return integer; + } + }); + } + }) + .test() + .assertFailure(CompositeException.class, 1, 2, 3, 23, 32); + } + + @Test + public void delayErrorCallableEager() { + Flowable.just(1, 2, 3, 101, 102, 23, 890, 120, 32) + .concatMapDelayError(new Function>() { + @Override public Flowable apply(final Integer integer) throws Exception { + return Flowable.fromCallable(new Callable() { + @Override public Integer call() throws Exception { + if (integer>= 100) { + throw new NullPointerException("test null exp"); + } + return integer; + } + }); + } + }, 2, false) + .test() + .assertFailure(NullPointerException.class, 1, 2, 3); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatTest.java index 772f559733..c1faba86d4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableConcatTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.Method; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java index 8df7c9c193..aacff6e5af 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDebounceTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; @@ -547,4 +546,14 @@ public void timedError() { .test() .assertFailure(TestException.class); } + + @Test + public void debounceOnEmpty() { + Flowable.empty().debounce(new Function>() { + @Override + public Publisher apply(Object o) { + return Flowable.just(new Object()); + } + }).subscribe(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelayTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelayTest.java index 401b9c62b4..cf1817e0e4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelayTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDelayTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDematerializeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDematerializeTest.java index 66fd932dcc..723d3b1e59 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDematerializeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDematerializeTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java index 448e963d49..86b6200440 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java index 7c5eb993e1..0ea9353505 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDistinctUntilChangedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnEachTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnEachTest.java index 4a9ac3eca2..e61fdc4b38 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnEachTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableDoOnEachTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java index 85093c4576..a6928a1157 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFilterTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java index 4c1441775b..e232af7bd4 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFlatMapTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -1124,4 +1123,61 @@ public void accept(Integer v) throws Exception { assertFalse(pp3.hasSubscribers()); assertFalse(pp4.hasSubscribers()); } + + @Test + public void mainErrorsInnerCancelled() { + PublishProcessor pp1 = PublishProcessor.create(); + PublishProcessor pp2 = PublishProcessor.create(); + + pp1 + .flatMap(Functions.justFunction(pp2)) + .test(); + + pp1.onNext(1); + assertTrue("No subscribers?", pp2.hasSubscribers()); + + pp1.onError(new TestException()); + + assertFalse("Has subscribers?", pp2.hasSubscribers()); + } + + @Test + public void innerErrorsMainCancelled() { + PublishProcessor pp1 = PublishProcessor.create(); + PublishProcessor pp2 = PublishProcessor.create(); + + pp1 + .flatMap(Functions.justFunction(pp2)) + .test(); + + pp1.onNext(1); + assertTrue("No subscribers?", pp2.hasSubscribers()); + + pp2.onError(new TestException()); + + assertFalse("Has subscribers?", pp1.hasSubscribers()); + } + + @Test(timeout = 5000) + public void mixedScalarAsync() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + Flowable + .range(0, 20) + .flatMap(new Function>() { + @Override + public Publisher apply(Integer integer) throws Exception { + if (integer % 5 != 0) { + return Flowable + .just(integer); + } + + return Flowable + .just(-integer) + .observeOn(Schedulers.computation()); + } + }, false, 1) + .ignoreElements() + .blockingAwait(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java index f3239001ee..254274b1cf 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromCallableTest.java @@ -17,7 +17,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java index e33556cb4e..d2e15fa60d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableFromIterableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java index 97a0a5ca23..cb52621bac 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupByTest.java @@ -30,12 +30,13 @@ import com.google.common.cache.*; import io.reactivex.*; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.flowables.GroupedFlowable; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; -import io.reactivex.internal.fuseable.QueueFuseable; +import io.reactivex.internal.fuseable.*; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; @@ -2206,4 +2207,147 @@ public void accept(Object object) { }}; return evictingMapFactory; } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor pp = PublishProcessor.create(); + + final AtomicReference>> qs = new AtomicReference>>(); + + final TestSubscriber ts2 = new TestSubscriber(); + + pp.groupBy(Functions.identity(), Functions.identity(), false, 4) + .subscribe(new FlowableSubscriber>() { + + boolean once; + + @Override + public void onNext(GroupedFlowable g) { + if (!once) { + try { + GroupedFlowable t = qs.get().poll(); + if (t != null) { + once = true; + t.subscribe(ts2); + } + } catch (Throwable ignored) { + // not relevant here + } + } + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + + @Override + public void onSubscribe(Subscription s) { + @SuppressWarnings("unchecked") + QueueSubscription> q = (QueueSubscription>)s; + qs.set(q); + q.requestFusion(QueueFuseable.ANY); + q.request(1); + } + }) + ; + + Runnable r1 = new Runnable() { + @Override + public void run() { + qs.get().cancel(); + qs.get().clear(); + } + }; + Runnable r2 = new Runnable() { + @Override + public void run() { + ts2.cancel(); + } + }; + + for (int i = 0; i < 100; i++) { + pp.onNext(i); + } + + TestHelper.race(r1, r2); + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + } finally { + RxJavaPlugins.reset(); + } + } + } + + @Test + public void fusedParallelGroupProcessing() { + Flowable.range(0, 500000) + .subscribeOn(Schedulers.single()) + .groupBy(new Function() { + @Override + public Integer apply(Integer i) { + return i % 2; + } + }) + .flatMap(new Function, Publisher>() { + @Override + public Publisher apply(GroupedFlowable g) { + return g.getKey() == 0 + ? g + .parallel() + .runOn(Schedulers.computation()) + .map(Functions.identity()) + .sequential() + : g.map(Functions.identity()) // no need to use hide + ; + } + }) + .test() + .awaitDone(20, TimeUnit.SECONDS) + .assertValueCount(500000) + .assertComplete() + .assertNoErrors(); + } + + @Test + public void cancelledGroupResumesRequesting() { + final List> tss = new ArrayList>(); + final AtomicInteger counter = new AtomicInteger(); + final AtomicBoolean done = new AtomicBoolean(); + Flowable.range(1, 1000) + .doOnNext(new Consumer() { + @Override + public void accept(Integer v) throws Exception { + counter.getAndIncrement(); + } + }) + .groupBy(Functions.justFunction(1)) + .subscribe(new Consumer>() { + @Override + public void accept(GroupedFlowable v) throws Exception { + TestSubscriber ts = TestSubscriber.create(0L); + tss.add(ts); + v.subscribe(ts); + } + }, Functions.emptyConsumer(), new Action() { + @Override + public void run() throws Exception { + done.set(true); + } + }); + + while (!done.get()) { + tss.remove(0).cancel(); + } + + assertEquals(1000, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java index b9e1b4f995..0704878805 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableGroupJoinTest.java @@ -16,7 +16,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java index af85b261c6..fbc4708710 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableHideTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import org.junit.Test; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableJoinTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableJoinTest.java index 14a5ec3d59..e8f71d7aae 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableJoinTest.java @@ -15,7 +15,6 @@ */ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java index 9ebc328d56..0ff960c4cc 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMapTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -24,7 +23,6 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java index 67129c956b..671abc314e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeDelayErrorTest.java @@ -25,7 +25,6 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.*; import io.reactivex.functions.LongConsumer; import io.reactivex.internal.subscriptions.BooleanSubscription; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java index 010da8bd69..34a732be7c 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeTest.java @@ -15,7 +15,6 @@ import static java.util.Arrays.asList; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.lang.reflect.Method; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletableTest.java index cf5b7917a6..18f5551e4a 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithCompletableTest.java @@ -136,4 +136,40 @@ public void run() { ts.assertResult(1); } } + + @Test + public void cancelOtherOnMainError() { + PublishProcessor pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestSubscriber ts = pp.mergeWith(cs).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(cs.hasObservers()); + + pp.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", cs.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishProcessor pp = PublishProcessor.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestSubscriber ts = pp.mergeWith(cs).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(cs.hasObservers()); + + cs.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", cs.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybeTest.java index c38bf6ae7b..676c9074c3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithMaybeTest.java @@ -401,4 +401,40 @@ public void onNext(Integer t) { ts.assertValueCount(Flowable.bufferSize()); ts.assertComplete(); } + + @Test + public void cancelOtherOnMainError() { + PublishProcessor pp = PublishProcessor.create(); + MaybeSubject ms = MaybeSubject.create(); + + TestSubscriber ts = pp.mergeWith(ms).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ms.hasObservers()); + + pp.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", ms.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishProcessor pp = PublishProcessor.create(); + MaybeSubject ms = MaybeSubject.create(); + + TestSubscriber ts = pp.mergeWith(ms).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ms.hasObservers()); + + ms.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", ms.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingleTest.java index 2ab0568a7e..6a182785da 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableMergeWithSingleTest.java @@ -397,4 +397,40 @@ public void onNext(Integer t) { ts.assertValueCount(Flowable.bufferSize()); ts.assertComplete(); } + + @Test + public void cancelOtherOnMainError() { + PublishProcessor pp = PublishProcessor.create(); + SingleSubject ss = SingleSubject.create(); + + TestSubscriber ts = pp.mergeWith(ss).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ss.hasObservers()); + + pp.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", ss.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishProcessor pp = PublishProcessor.create(); + SingleSubject ss = SingleSubject.create(); + + TestSubscriber ts = pp.mergeWith(ss).test(); + + assertTrue(pp.hasSubscribers()); + assertTrue(ss.hasObservers()); + + ss.onError(new TestException()); + + ts.assertFailure(TestException.class); + + assertFalse("main has observers!", pp.hasSubscribers()); + assertFalse("other has observers", ss.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java index ca4bd28986..a5abff03ef 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableObserveOnTest.java @@ -35,6 +35,7 @@ import io.reactivex.internal.operators.flowable.FlowableObserveOn.BaseObserveOnSubscriber; import io.reactivex.internal.schedulers.ImmediateThinScheduler; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.processors.*; import io.reactivex.schedulers.*; @@ -1941,4 +1942,37 @@ public void workerNotDisposedPrematurelyNormalInAsyncOutConditional() { assertEquals(1, s.disposedCount.get()); } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List errors = TestHelper.trackPluginErrors(); + try { + final UnicastProcessor up = UnicastProcessor.create(); + + TestObserver to = up.hide() + .observeOn(Schedulers.io()) + .observeOn(Schedulers.single()) + .unsubscribeOn(Schedulers.computation()) + .firstOrError() + .test(); + + for (int i = 0; up.hasSubscribers() && i < 10000; i++) { + up.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferTest.java index 84793d3136..be7fe522da 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureBufferTest.java @@ -15,17 +15,22 @@ import static org.junit.Assert.*; +import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.reactivestreams.*; -import io.reactivex.Flowable; +import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; @@ -143,7 +148,7 @@ public void run() { int size = ts.values().size(); assertTrue(size <= 150); // will get up to 50 more - assertTrue(ts.values().get(size - 1) == size - 1); + assertEquals((long)ts.values().get(size - 1), size - 1); } static final Flowable infinite = Flowable.unsafeCreate(new Publisher() { @@ -307,4 +312,37 @@ public void fusionRejected() { SubscriberFusion.assertFusion(ts, QueueFuseable.NONE) .assertEmpty(); } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List errors = TestHelper.trackPluginErrors(); + try { + final PublishProcessor pp = PublishProcessor.create(); + + TestObserver to = pp.onBackpressureBuffer(4, false, true) + .observeOn(Schedulers.io()) + .map(Functions.identity()) + .observeOn(Schedulers.single()) + .firstOrError() + .test(); + + for (int i = 0; pp.hasSubscribers(); i++) { + pp.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureErrorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureErrorTest.java index 3be409e862..9afb864d40 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureErrorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnBackpressureErrorTest.java @@ -13,11 +13,16 @@ package io.reactivex.internal.operators.flowable; +import static org.junit.Assert.*; + import org.junit.Test; import org.reactivestreams.Publisher; import io.reactivex.*; +import io.reactivex.exceptions.MissingBackpressureException; import io.reactivex.functions.Function; +import io.reactivex.subjects.PublishSubject; +import io.reactivex.subscribers.TestSubscriber; public class FlowableOnBackpressureErrorTest { @@ -50,4 +55,20 @@ public Object apply(Flowable f) throws Exception { } }, false, 1, 1, 1); } + + @Test + public void overflowCancels() { + PublishSubject ps = PublishSubject.create(); + + TestSubscriber ts = ps.toFlowable(BackpressureStrategy.ERROR) + .test(0L); + + assertTrue(ps.hasObservers()); + + ps.onNext(1); + + assertFalse(ps.hasObservers()); + + ts.assertFailure(MissingBackpressureException.class); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFunctionTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFunctionTest.java index 06f10e835d..8d3cef82d0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFunctionTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableOnErrorResumeNextViaFunctionTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishAltTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishAltTest.java new file mode 100644 index 0000000000..414aa79c07 --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishAltTest.java @@ -0,0 +1,1629 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.*; +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.Disposable; +import io.reactivex.exceptions.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.HasUpstreamPublisher; +import io.reactivex.internal.operators.flowable.FlowablePublish.*; +import io.reactivex.internal.schedulers.ImmediateThinScheduler; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.PublishProcessor; +import io.reactivex.schedulers.*; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowablePublishAltTest { + + @Test + public void testPublish() throws InterruptedException { + final AtomicInteger counter = new AtomicInteger(); + ConnectableFlowable f = Flowable.unsafeCreate(new Publisher() { + + @Override + public void subscribe(final Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + new Thread(new Runnable() { + + @Override + public void run() { + counter.incrementAndGet(); + subscriber.onNext("one"); + subscriber.onComplete(); + } + }).start(); + } + }).publish(); + + final CountDownLatch latch = new CountDownLatch(2); + + // subscribe once + f.subscribe(new Consumer() { + + @Override + public void accept(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + // subscribe again + f.subscribe(new Consumer() { + + @Override + public void accept(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + Disposable connection = f.connect(); + try { + if (!latch.await(1000, TimeUnit.MILLISECONDS)) { + fail("subscriptions did not receive values"); + } + assertEquals(1, counter.get()); + } finally { + connection.dispose(); + } + } + + @Test + public void testBackpressureFastSlow() { + ConnectableFlowable is = Flowable.range(1, Flowable.bufferSize() * 2).publish(); + Flowable fast = is.observeOn(Schedulers.computation()) + .doOnComplete(new Action() { + @Override + public void run() { + System.out.println("^^^^^^^^^^^^^ completed FAST"); + } + }); + + Flowable slow = is.observeOn(Schedulers.computation()).map(new Function() { + int c; + + @Override + public Integer apply(Integer i) { + if (c == 0) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + } + c++; + return i; + } + + }).doOnComplete(new Action() { + + @Override + public void run() { + System.out.println("^^^^^^^^^^^^^ completed SLOW"); + } + + }); + + TestSubscriber ts = new TestSubscriber(); + Flowable.merge(fast, slow).subscribe(ts); + is.connect(); + ts.awaitTerminalEvent(); + ts.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 4, ts.valueCount()); + } + + // use case from https://github.com/ReactiveX/RxJava/issues/1732 + @Test + public void testTakeUntilWithPublishedStreamUsingSelector() { + final AtomicInteger emitted = new AtomicInteger(); + Flowable xs = Flowable.range(0, Flowable.bufferSize() * 2).doOnNext(new Consumer() { + + @Override + public void accept(Integer t1) { + emitted.incrementAndGet(); + } + + }); + TestSubscriber ts = new TestSubscriber(); + xs.publish(new Function, Flowable>() { + + @Override + public Flowable apply(Flowable xs) { + return xs.takeUntil(xs.skipWhile(new Predicate() { + + @Override + public boolean test(Integer i) { + return i <= 3; + } + + })); + } + + }).subscribe(ts); + ts.awaitTerminalEvent(); + ts.assertNoErrors(); + ts.assertValues(0, 1, 2, 3); + assertEquals(5, emitted.get()); + System.out.println(ts.values()); + } + + // use case from https://github.com/ReactiveX/RxJava/issues/1732 + @Test + public void testTakeUntilWithPublishedStream() { + Flowable xs = Flowable.range(0, Flowable.bufferSize() * 2); + TestSubscriber ts = new TestSubscriber(); + ConnectableFlowable xsp = xs.publish(); + xsp.takeUntil(xsp.skipWhile(new Predicate() { + + @Override + public boolean test(Integer i) { + return i <= 3; + } + + })).subscribe(ts); + xsp.connect(); + System.out.println(ts.values()); + } + + @Test(timeout = 10000) + public void testBackpressureTwoConsumers() { + final AtomicInteger sourceEmission = new AtomicInteger(); + final AtomicBoolean sourceUnsubscribed = new AtomicBoolean(); + final Flowable source = Flowable.range(1, 100) + .doOnNext(new Consumer() { + @Override + public void accept(Integer t1) { + sourceEmission.incrementAndGet(); + } + }) + .doOnCancel(new Action() { + @Override + public void run() { + sourceUnsubscribed.set(true); + } + }).share(); + ; + + final AtomicBoolean child1Unsubscribed = new AtomicBoolean(); + final AtomicBoolean child2Unsubscribed = new AtomicBoolean(); + + final TestSubscriber ts2 = new TestSubscriber(); + + final TestSubscriber ts1 = new TestSubscriber() { + @Override + public void onNext(Integer t) { + if (valueCount() == 2) { + source.doOnCancel(new Action() { + @Override + public void run() { + child2Unsubscribed.set(true); + } + }).take(5).subscribe(ts2); + } + super.onNext(t); + } + }; + + source.doOnCancel(new Action() { + @Override + public void run() { + child1Unsubscribed.set(true); + } + }).take(5) + .subscribe(ts1); + + ts1.awaitTerminalEvent(); + ts2.awaitTerminalEvent(); + + ts1.assertNoErrors(); + ts2.assertNoErrors(); + + assertTrue(sourceUnsubscribed.get()); + assertTrue(child1Unsubscribed.get()); + assertTrue(child2Unsubscribed.get()); + + ts1.assertValues(1, 2, 3, 4, 5); + ts2.assertValues(4, 5, 6, 7, 8); + + assertEquals(8, sourceEmission.get()); + } + + @Test + public void testConnectWithNoSubscriber() { + TestScheduler scheduler = new TestScheduler(); + ConnectableFlowable cf = Flowable.interval(10, 10, TimeUnit.MILLISECONDS, scheduler).take(3).publish(); + cf.connect(); + // Emit 0 + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + TestSubscriber subscriber = new TestSubscriber(); + cf.subscribe(subscriber); + // Emit 1 and 2 + scheduler.advanceTimeBy(50, TimeUnit.MILLISECONDS); + subscriber.assertValues(1L, 2L); + subscriber.assertNoErrors(); + subscriber.assertTerminated(); + } + + @Test + public void testSubscribeAfterDisconnectThenConnect() { + ConnectableFlowable source = Flowable.just(1).publish(); + + TestSubscriber ts1 = new TestSubscriber(); + + source.subscribe(ts1); + + Disposable connection = source.connect(); + + ts1.assertValue(1); + ts1.assertNoErrors(); + ts1.assertTerminated(); + + TestSubscriber ts2 = new TestSubscriber(); + + source.subscribe(ts2); + + Disposable connection2 = source.connect(); + + ts2.assertValue(1); + ts2.assertNoErrors(); + ts2.assertTerminated(); + + System.out.println(connection); + System.out.println(connection2); + } + + @Test + public void testNoSubscriberRetentionOnCompleted() { + FlowablePublish source = (FlowablePublish)Flowable.just(1).publish(); + + TestSubscriber ts1 = new TestSubscriber(); + + source.subscribe(ts1); + + ts1.assertNoValues(); + ts1.assertNoErrors(); + ts1.assertNotComplete(); + + source.connect(); + + ts1.assertValue(1); + ts1.assertNoErrors(); + ts1.assertTerminated(); + + assertNull(source.current.get()); + } + + @Test + public void testNonNullConnection() { + ConnectableFlowable source = Flowable.never().publish(); + + assertNotNull(source.connect()); + assertNotNull(source.connect()); + } + + @Test + public void testNoDisconnectSomeoneElse() { + ConnectableFlowable source = Flowable.never().publish(); + + Disposable connection1 = source.connect(); + Disposable connection2 = source.connect(); + + connection1.dispose(); + + Disposable connection3 = source.connect(); + + connection2.dispose(); + + assertTrue(checkPublishDisposed(connection1)); + assertTrue(checkPublishDisposed(connection2)); + assertFalse(checkPublishDisposed(connection3)); + } + + @SuppressWarnings("unchecked") + static boolean checkPublishDisposed(Disposable d) { + return ((FlowablePublish.PublishSubscriber)d).isDisposed(); + } + + @Test + public void testZeroRequested() { + ConnectableFlowable source = Flowable.just(1).publish(); + + TestSubscriber ts = new TestSubscriber(0L); + + source.subscribe(ts); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotComplete(); + + source.connect(); + + ts.assertNoValues(); + ts.assertNoErrors(); + ts.assertNotComplete(); + + ts.request(5); + + ts.assertValue(1); + ts.assertNoErrors(); + ts.assertTerminated(); + } + + @Test + public void testConnectIsIdempotent() { + final AtomicInteger calls = new AtomicInteger(); + Flowable source = Flowable.unsafeCreate(new Publisher() { + @Override + public void subscribe(Subscriber t) { + t.onSubscribe(new BooleanSubscription()); + calls.getAndIncrement(); + } + }); + + ConnectableFlowable conn = source.publish(); + + assertEquals(0, calls.get()); + + conn.connect(); + conn.connect(); + + assertEquals(1, calls.get()); + + conn.connect().dispose(); + + conn.connect(); + conn.connect(); + + assertEquals(2, calls.get()); + } + + @Test + public void syncFusedObserveOn() { + ConnectableFlowable cf = Flowable.range(0, 1000).publish(); + Flowable obs = cf.observeOn(Schedulers.computation()); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List> tss = new ArrayList>(); + for (int k = 1; k < j; k++) { + TestSubscriber ts = new TestSubscriber(); + tss.add(ts); + obs.subscribe(ts); + } + + Disposable connection = cf.connect(); + + for (TestSubscriber ts : tss) { + ts.awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(1000) + .assertNoErrors() + .assertComplete(); + } + connection.dispose(); + } + } + } + + @Test + public void syncFusedObserveOn2() { + ConnectableFlowable cf = Flowable.range(0, 1000).publish(); + Flowable obs = cf.observeOn(ImmediateThinScheduler.INSTANCE); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List> tss = new ArrayList>(); + for (int k = 1; k < j; k++) { + TestSubscriber ts = new TestSubscriber(); + tss.add(ts); + obs.subscribe(ts); + } + + Disposable connection = cf.connect(); + + for (TestSubscriber ts : tss) { + ts.awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(1000) + .assertNoErrors() + .assertComplete(); + } + connection.dispose(); + } + } + } + + @Test + public void asyncFusedObserveOn() { + ConnectableFlowable cf = Flowable.range(0, 1000).observeOn(ImmediateThinScheduler.INSTANCE).publish(); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List> tss = new ArrayList>(); + for (int k = 1; k < j; k++) { + TestSubscriber ts = new TestSubscriber(); + tss.add(ts); + cf.subscribe(ts); + } + + Disposable connection = cf.connect(); + + for (TestSubscriber ts : tss) { + ts.awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(1000) + .assertNoErrors() + .assertComplete(); + } + connection.dispose(); + } + } + } + + @Test + public void testObserveOn() { + ConnectableFlowable cf = Flowable.range(0, 1000).hide().publish(); + Flowable obs = cf.observeOn(Schedulers.computation()); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List> tss = new ArrayList>(); + for (int k = 1; k < j; k++) { + TestSubscriber ts = new TestSubscriber(); + tss.add(ts); + obs.subscribe(ts); + } + + Disposable connection = cf.connect(); + + for (TestSubscriber ts : tss) { + ts.awaitDone(5, TimeUnit.SECONDS) + .assertSubscribed() + .assertValueCount(1000) + .assertNoErrors() + .assertComplete(); + } + connection.dispose(); + } + } + } + + @Test + public void source() { + Flowable f = Flowable.never(); + + assertSame(f, (((HasUpstreamPublisher)f.publish()).source())); + } + + @Test + public void connectThrows() { + ConnectableFlowable cf = Flowable.empty().publish(); + try { + cf.connect(new Consumer() { + @Override + public void accept(Disposable d) throws Exception { + throw new TestException(); + } + }); + } catch (TestException ex) { + // expected + } + } + + @Test + public void addRemoveRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableFlowable cf = Flowable.empty().publish(); + + final TestSubscriber ts = cf.test(); + + final TestSubscriber ts2 = new TestSubscriber(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cf.subscribe(ts2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void disposeOnArrival() { + ConnectableFlowable cf = Flowable.empty().publish(); + + cf.test(Long.MAX_VALUE, true).assertEmpty(); + } + + @Test + public void disposeOnArrival2() { + Flowable co = Flowable.never().publish().autoConnect(); + + co.test(Long.MAX_VALUE, true).assertEmpty(); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(Flowable.never().publish()); + + TestHelper.checkDisposed(Flowable.never().publish(Functions.identity())); + } + + @Test + public void empty() { + ConnectableFlowable cf = Flowable.empty().publish(); + + cf.connect(); + } + + @Test + public void take() { + ConnectableFlowable cf = Flowable.range(1, 2).publish(); + + TestSubscriber ts = cf.take(1).test(); + + cf.connect(); + + ts.assertResult(1); + } + + @Test + public void just() { + final PublishProcessor pp = PublishProcessor.create(); + + ConnectableFlowable cf = pp.publish(); + + TestSubscriber ts = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + pp.onComplete(); + } + }; + + cf.subscribe(ts); + cf.connect(); + + pp.onNext(1); + + ts.assertResult(1); + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final PublishProcessor pp = PublishProcessor.create(); + + final ConnectableFlowable cf = pp.publish(); + + final TestSubscriber ts = cf.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void badSource() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onNext(1); + subscriber.onComplete(); + subscriber.onNext(2); + subscriber.onError(new TestException()); + subscriber.onComplete(); + } + } + .publish() + .autoConnect() + .test() + .assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void noErrorLoss() { + List errors = TestHelper.trackPluginErrors(); + try { + ConnectableFlowable cf = Flowable.error(new TestException()).publish(); + + cf.connect(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void subscribeDisconnectRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final PublishProcessor pp = PublishProcessor.create(); + + final ConnectableFlowable cf = pp.publish(); + + final Disposable d = cf.connect(); + final TestSubscriber ts = new TestSubscriber(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + d.dispose(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + cf.subscribe(ts); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void selectorDisconnectsIndependentSource() { + PublishProcessor pp = PublishProcessor.create(); + + pp.publish(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) throws Exception { + return Flowable.range(1, 2); + } + }) + .test() + .assertResult(1, 2); + + assertFalse(pp.hasSubscribers()); + } + + @Test(timeout = 5000) + public void selectorLatecommer() { + Flowable.range(1, 5) + .publish(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) throws Exception { + return v.concatWith(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Flowable.error(new TestException()) + .publish(Functions.identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorInnerError() { + PublishProcessor pp = PublishProcessor.create(); + + pp.publish(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) throws Exception { + return Flowable.error(new TestException()); + } + }) + .test() + .assertFailure(TestException.class); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void preNextConnect() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableFlowable cf = Flowable.empty().publish(); + + cf.connect(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cf.test(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void connectRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableFlowable cf = Flowable.empty().publish(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + cf.connect(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void selectorCrash() { + Flowable.just(1).publish(new Function, Flowable>() { + @Override + public Flowable apply(Flowable v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void pollThrows() { + Flowable.just(1) + .map(new Function() { + @Override + public Object apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.flowableStripBoundary()) + .publish() + .autoConnect() + .test() + .assertFailure(TestException.class); + } + + @Test + public void pollThrowsNoSubscribers() { + ConnectableFlowable cf = Flowable.just(1, 2) + .map(new Function() { + @Override + public Integer apply(Integer v) throws Exception { + if (v == 2) { + throw new TestException(); + } + return v; + } + }) + .compose(TestHelper.flowableStripBoundary()) + .publish(); + + TestSubscriber ts = cf.take(1) + .test(); + + cf.connect(); + + ts.assertResult(1); + } + + @Test + public void dryRunCrash() { + List errors = TestHelper.trackPluginErrors(); + try { + final TestSubscriber ts = new TestSubscriber(1L) { + @Override + public void onNext(Object t) { + super.onNext(t); + onComplete(); + cancel(); + } + }; + + Flowable.range(1, 10) + .map(new Function() { + @Override + public Object apply(Integer v) throws Exception { + if (v == 2) { + throw new TestException(); + } + return v; + } + }) + .publish() + .autoConnect() + .subscribe(ts); + + ts + .assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void overflowQueue() { + List errors = TestHelper.trackPluginErrors(); + try { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter s) throws Exception { + for (int i = 0; i < 10; i++) { + s.onNext(i); + } + } + }, BackpressureStrategy.MISSING) + .publish(8) + .autoConnect() + .test(0L) + .assertFailure(MissingBackpressureException.class); + + TestHelper.assertError(errors, 0, MissingBackpressureException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void delayedUpstreamOnSubscribe() { + final Subscriber[] sub = { null }; + + new Flowable() { + @Override + protected void subscribeActual(Subscriber s) { + sub[0] = s; + } + } + .publish() + .connect() + .dispose(); + + BooleanSubscription bs = new BooleanSubscription(); + + sub[0].onSubscribe(bs); + + assertTrue(bs.isCancelled()); + } + + @Test + public void disposeRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final AtomicReference ref = new AtomicReference(); + + final ConnectableFlowable cf = new Flowable() { + @Override + protected void subscribeActual(Subscriber s) { + s.onSubscribe(new BooleanSubscription()); + ref.set((Disposable)s); + } + }.publish(); + + cf.connect(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ref.get().dispose(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void removeNotPresent() { + final AtomicReference> ref = new AtomicReference>(); + + final ConnectableFlowable cf = new Flowable() { + @Override + @SuppressWarnings("unchecked") + protected void subscribeActual(Subscriber s) { + s.onSubscribe(new BooleanSubscription()); + ref.set((PublishSubscriber)s); + } + }.publish(); + + cf.connect(); + + ref.get().add(new InnerSubscriber(new TestSubscriber())); + ref.get().remove(null); + } + + @Test + @Ignore("publish() keeps consuming the upstream if there are no subscribers, 3.x should change this") + public void subscriberSwap() { + final ConnectableFlowable cf = Flowable.range(1, 5).publish(); + + cf.connect(); + + TestSubscriber ts1 = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + cancel(); + onComplete(); + } + }; + + cf.subscribe(ts1); + + ts1.assertResult(1); + + TestSubscriber ts2 = new TestSubscriber(0); + cf.subscribe(ts2); + + ts2 + .assertEmpty() + .requestMore(4) + .assertResult(2, 3, 4, 5); + } + + @Test + public void subscriberLiveSwap() { + final ConnectableFlowable cf = new FlowablePublishAlt(Flowable.range(1, 5), 128); + + final TestSubscriber ts2 = new TestSubscriber(0); + + TestSubscriber ts1 = new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + cancel(); + onComplete(); + cf.subscribe(ts2); + } + }; + + cf.subscribe(ts1); + + cf.connect(); + + ts1.assertResult(1); + + ts2 + .assertEmpty() + .requestMore(4) + .assertResult(2, 3, 4, 5); + } + + @Test + public void selectorSubscriberSwap() { + final AtomicReference> ref = new AtomicReference>(); + + Flowable.range(1, 5).publish(new Function, Publisher>() { + @Override + public Publisher apply(Flowable f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + ref.get().take(2).test().assertResult(1, 2); + + ref.get() + .test(0) + .assertEmpty() + .requestMore(2) + .assertValuesOnly(3, 4) + .requestMore(1) + .assertResult(3, 4, 5); + } + + @Test + public void leavingSubscriberOverrequests() { + final AtomicReference> ref = new AtomicReference>(); + + PublishProcessor pp = PublishProcessor.create(); + + pp.publish(new Function, Publisher>() { + @Override + public Publisher apply(Flowable f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + TestSubscriber ts1 = ref.get().take(2).test(); + + pp.onNext(1); + pp.onNext(2); + + ts1.assertResult(1, 2); + + pp.onNext(3); + pp.onNext(4); + + TestSubscriber ts2 = ref.get().test(0L); + + ts2.assertEmpty(); + + ts2.requestMore(2); + + ts2.assertValuesOnly(3, 4); + } + + // call a transformer only if the input is non-empty + @Test + public void composeIfNotEmpty() { + final FlowableTransformer transformer = new FlowableTransformer() { + @Override + public Publisher apply(Flowable g) { + return g.map(new Function() { + @Override + public Integer apply(Integer v) throws Exception { + return v + 1; + } + }); + } + }; + + final AtomicInteger calls = new AtomicInteger(); + Flowable.range(1, 5) + .publish(new Function, Publisher>() { + @Override + public Publisher apply(final Flowable shared) + throws Exception { + return shared.take(1).concatMap(new Function>() { + @Override + public Publisher apply(Integer first) + throws Exception { + calls.incrementAndGet(); + return transformer.apply(Flowable.just(first).concatWith(shared)); + } + }); + } + }) + .test() + .assertResult(2, 3, 4, 5, 6); + + assertEquals(1, calls.get()); + } + + // call a transformer only if the input is non-empty + @Test + public void composeIfNotEmptyNotFused() { + final FlowableTransformer transformer = new FlowableTransformer() { + @Override + public Publisher apply(Flowable g) { + return g.map(new Function() { + @Override + public Integer apply(Integer v) throws Exception { + return v + 1; + } + }); + } + }; + + final AtomicInteger calls = new AtomicInteger(); + Flowable.range(1, 5).hide() + .publish(new Function, Publisher>() { + @Override + public Publisher apply(final Flowable shared) + throws Exception { + return shared.take(1).concatMap(new Function>() { + @Override + public Publisher apply(Integer first) + throws Exception { + calls.incrementAndGet(); + return transformer.apply(Flowable.just(first).concatWith(shared)); + } + }); + } + }) + .test() + .assertResult(2, 3, 4, 5, 6); + + assertEquals(1, calls.get()); + } + + // call a transformer only if the input is non-empty + @Test + public void composeIfNotEmptyIsEmpty() { + final FlowableTransformer transformer = new FlowableTransformer() { + @Override + public Publisher apply(Flowable g) { + return g.map(new Function() { + @Override + public Integer apply(Integer v) throws Exception { + return v + 1; + } + }); + } + }; + + final AtomicInteger calls = new AtomicInteger(); + Flowable.empty().hide() + .publish(new Function, Publisher>() { + @Override + public Publisher apply(final Flowable shared) + throws Exception { + return shared.take(1).concatMap(new Function>() { + @Override + public Publisher apply(Integer first) + throws Exception { + calls.incrementAndGet(); + return transformer.apply(Flowable.just(first).concatWith(shared)); + } + }); + } + }) + .test() + .assertResult(); + + assertEquals(0, calls.get()); + } + + @Test + public void publishFunctionCancelOuterAfterOneInner() { + final AtomicReference> ref = new AtomicReference>(); + + PublishProcessor pp = PublishProcessor.create(); + + final TestSubscriber ts = pp.publish(new Function, Publisher>() { + @Override + public Publisher apply(Flowable f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + ref.get().subscribe(new TestSubscriber() { + @Override + public void onNext(Integer t) { + super.onNext(t); + onComplete(); + ts.cancel(); + } + }); + + pp.onNext(1); + } + + @Test + public void publishFunctionCancelOuterAfterOneInnerBackpressured() { + final AtomicReference> ref = new AtomicReference>(); + + PublishProcessor pp = PublishProcessor.create(); + + final TestSubscriber ts = pp.publish(new Function, Publisher>() { + @Override + public Publisher apply(Flowable f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + ref.get().subscribe(new TestSubscriber(1L) { + @Override + public void onNext(Integer t) { + super.onNext(t); + onComplete(); + ts.cancel(); + } + }); + + pp.onNext(1); + } + + @Test + public void publishCancelOneAsync() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + + final PublishProcessor pp = PublishProcessor.create(); + + final AtomicReference> ref = new AtomicReference>(); + + pp.publish(new Function, Publisher>() { + @Override + public Publisher apply(Flowable f) throws Exception { + ref.set(f); + return Flowable.never(); + } + }).test(); + + final TestSubscriber ts1 = ref.get().test(); + TestSubscriber ts2 = ref.get().test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + pp.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + ts1.cancel(); + } + }; + + TestHelper.race(r1, r2); + + ts2.assertValuesOnly(1); + } + } + + @Test + public void publishCancelOneAsync2() { + final PublishProcessor pp = PublishProcessor.create(); + + ConnectableFlowable cf = pp.publish(); + + final TestSubscriber ts1 = new TestSubscriber(); + + final AtomicReference> ref = new AtomicReference>(); + + cf.subscribe(new FlowableSubscriber() { + @SuppressWarnings("unchecked") + @Override + public void onSubscribe(Subscription s) { + ts1.onSubscribe(new BooleanSubscription()); + // pretend to be cancelled without removing it from the subscriber list + ref.set((InnerSubscriber)s); + } + + @Override + public void onNext(Integer t) { + ts1.onNext(t); + } + + @Override + public void onError(Throwable t) { + ts1.onError(t); + } + + @Override + public void onComplete() { + ts1.onComplete(); + } + }); + TestSubscriber ts2 = cf.test(); + + cf.connect(); + + ref.get().set(Long.MIN_VALUE); + + pp.onNext(1); + + ts1.assertEmpty(); + ts2.assertValuesOnly(1); + } + + @Test + public void boundaryFusion() { + Flowable.range(1, 10000) + .observeOn(Schedulers.single()) + .map(new Function() { + @Override + public String apply(Integer t) throws Exception { + String name = Thread.currentThread().getName(); + if (name.contains("RxSingleScheduler")) { + return "RxSingleScheduler"; + } + return name; + } + }) + .share() + .observeOn(Schedulers.computation()) + .distinct() + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult("RxSingleScheduler"); + } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.range(1, 5).publish()); + } + + @Test + @SuppressWarnings("unchecked") + public void splitCombineSubscriberChangeAfterOnNext() { + Flowable source = Flowable.range(0, 20) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription v) throws Exception { + System.out.println("Subscribed"); + } + }) + .publish(10) + .refCount() + ; + + Flowable evenNumbers = source.filter(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 == 0; + } + }); + + Flowable oddNumbers = source.filter(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 != 0; + } + }); + + final Single getNextOdd = oddNumbers.first(0); + + TestSubscriber> ts = evenNumbers.concatMap(new Function>>() { + @Override + public Publisher> apply(Integer v) throws Exception { + return Single.zip( + Single.just(v), getNextOdd, + new BiFunction>() { + @Override + public List apply(Integer a, Integer b) throws Exception { + return Arrays.asList( a, b ); + } + } + ) + .toFlowable(); + } + }) + .takeWhile(new Predicate>() { + @Override + public boolean test(List v) throws Exception { + return v.get(0) < 20; + } + }) + .test(); + + ts + .assertResult( + Arrays.asList(0, 1), + Arrays.asList(2, 3), + Arrays.asList(4, 5), + Arrays.asList(6, 7), + Arrays.asList(8, 9), + Arrays.asList(10, 11), + Arrays.asList(12, 13), + Arrays.asList(14, 15), + Arrays.asList(16, 17), + Arrays.asList(18, 19) + ); + } + + @Test + @SuppressWarnings("unchecked") + public void splitCombineSubscriberChangeAfterOnNextFused() { + Flowable source = Flowable.range(0, 20) + .publish(10) + .refCount() + ; + + Flowable evenNumbers = source.filter(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 == 0; + } + }); + + Flowable oddNumbers = source.filter(new Predicate() { + @Override + public boolean test(Integer v) throws Exception { + return v % 2 != 0; + } + }); + + final Single getNextOdd = oddNumbers.first(0); + + TestSubscriber> ts = evenNumbers.concatMap(new Function>>() { + @Override + public Publisher> apply(Integer v) throws Exception { + return Single.zip( + Single.just(v), getNextOdd, + new BiFunction>() { + @Override + public List apply(Integer a, Integer b) throws Exception { + return Arrays.asList( a, b ); + } + } + ) + .toFlowable(); + } + }) + .takeWhile(new Predicate>() { + @Override + public boolean test(List v) throws Exception { + return v.get(0) < 20; + } + }) + .test(); + + ts + .assertResult( + Arrays.asList(0, 1), + Arrays.asList(2, 3), + Arrays.asList(4, 5), + Arrays.asList(6, 7), + Arrays.asList(8, 9), + Arrays.asList(10, 11), + Arrays.asList(12, 13), + Arrays.asList(14, 15), + Arrays.asList(16, 17), + Arrays.asList(18, 19) + ); + } + + @Test + public void altConnectCrash() { + try { + new FlowablePublishAlt(Flowable.empty(), 128) + .connect(new Consumer() { + @Override + public void accept(Disposable t) throws Exception { + throw new TestException(); + } + }); + fail("Should have thrown"); + } catch (TestException expected) { + // expected + } + } + + @Test + public void altConnectRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final ConnectableFlowable cf = + new FlowablePublishAlt(Flowable.never(), 128); + + Runnable r = new Runnable() { + @Override + public void run() { + cf.connect(); + } + }; + + TestHelper.race(r, r); + } + } + + @Test + public void fusedPollCrash() { + Flowable.range(1, 5) + .map(new Function() { + @Override + public Object apply(Integer v) throws Exception { + throw new TestException(); + } + }) + .compose(TestHelper.flowableStripBoundary()) + .publish() + .refCount() + .test() + .assertFailure(TestException.class); + } + + @Test + public void syncFusedNoRequest() { + Flowable.range(1, 5) + .publish(1) + .refCount() + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void normalBackpressuredPolls() { + Flowable.range(1, 5) + .hide() + .publish(1) + .refCount() + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void emptyHidden() { + Flowable.empty() + .hide() + .publish(1) + .refCount() + .test() + .assertResult(); + } + + @Test + public void emptyFused() { + Flowable.empty() + .publish(1) + .refCount() + .test() + .assertResult(); + } + + @Test + public void overflowQueueRefCount() { + new Flowable() { + @Override + protected void subscribeActual(Subscriber s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(1); + s.onNext(2); + } + } + .publish(1) + .refCount() + .test(0) + .requestMore(1) + .assertFailure(MissingBackpressureException.class, 1); + } + + @Test + public void doubleErrorRefCount() { + List errors = TestHelper.trackPluginErrors(); + try { + new Flowable() { + @Override + protected void subscribeActual(Subscriber s) { + s.onSubscribe(new BooleanSubscription()); + s.onError(new TestException("one")); + s.onError(new TestException("two")); + } + } + .publish(1) + .refCount() + .test(0) + .assertFailureAndMessage(TestException.class, "one"); + + TestHelper.assertUndeliverable(errors, 0, TestException.class, "two"); + assertEquals(1, errors.size()); + } finally { + RxJavaPlugins.reset(); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java index eac12749f5..80af00c66f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowablePublishTest.java @@ -39,6 +39,28 @@ public class FlowablePublishTest { + // This will undo the workaround so that the plain ObservablePublish is still + // tested. + @Before + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void before() { + RxJavaPlugins.setOnConnectableFlowableAssembly(new Function() { + @Override + public ConnectableFlowable apply(ConnectableFlowable co) throws Exception { + if (co instanceof FlowablePublishAlt) { + FlowablePublishAlt fpa = (FlowablePublishAlt) co; + return FlowablePublish.create(Flowable.fromPublisher(fpa.source()), fpa.publishBufferSize()); + } + return co; + } + }); + } + + @After + public void after() { + RxJavaPlugins.setOnConnectableFlowableAssembly(null); + } + @Test public void testPublish() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java index 470e0e8cc0..e3d67bb8a2 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeLongTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeTest.java index b0af47585b..3772aefd85 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRangeTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java index 37d7012065..af88de841f 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReduceTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountAltTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountAltTest.java new file mode 100644 index 0000000000..c8eca05dca --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountAltTest.java @@ -0,0 +1,1465 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.internal.operators.flowable; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.Test; +import org.mockito.InOrder; +import org.reactivestreams.*; + +import io.reactivex.*; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; +import io.reactivex.flowables.ConnectableFlowable; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.flowable.FlowableRefCount.RefConnection; +import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.processors.*; +import io.reactivex.schedulers.*; +import io.reactivex.subscribers.TestSubscriber; + +public class FlowableRefCountAltTest { + + @Test + public void testRefCountAsync() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger nextCount = new AtomicInteger(); + Flowable r = Flowable.interval(0, 20, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription s) { + subscribeCount.incrementAndGet(); + } + }) + .doOnNext(new Consumer() { + @Override + public void accept(Long l) { + nextCount.incrementAndGet(); + } + }) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + Disposable d1 = r.subscribe(new Consumer() { + @Override + public void accept(Long l) { + receivedCount.incrementAndGet(); + } + }); + + Disposable d2 = r.subscribe(); + + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + + for (;;) { + int a = nextCount.get(); + int b = receivedCount.get(); + if (a> 10 && a < 20 && a == b) { + break; + } + if (a>= 20) { + break; + } + try { + Thread.sleep(20); + } catch (InterruptedException e) { + } + } + // give time to emit + + // now unsubscribe + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one subscriber getting a value but not the other + d1.dispose(); + + System.out.println("onNext: " + nextCount.get()); + + // should emit once for both subscribers + assertEquals(nextCount.get(), receivedCount.get()); + // only 1 subscribe + assertEquals(1, subscribeCount.get()); + } + + @Test + public void testRefCountSynchronous() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger nextCount = new AtomicInteger(); + Flowable r = Flowable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription s) { + subscribeCount.incrementAndGet(); + } + }) + .doOnNext(new Consumer() { + @Override + public void accept(Integer l) { + nextCount.incrementAndGet(); + } + }) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + Disposable d1 = r.subscribe(new Consumer() { + @Override + public void accept(Integer l) { + receivedCount.incrementAndGet(); + } + }); + + Disposable d2 = r.subscribe(); + + // give time to emit + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + + // now unsubscribe + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one subscriber getting a value but not the other + d1.dispose(); + + System.out.println("onNext Count: " + nextCount.get()); + + // it will emit twice because it is synchronous + assertEquals(nextCount.get(), receivedCount.get() * 2); + // it will subscribe twice because it is synchronous + assertEquals(2, subscribeCount.get()); + } + + @Test + public void testRefCountSynchronousTake() { + final AtomicInteger nextCount = new AtomicInteger(); + Flowable r = Flowable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) + .doOnNext(new Consumer() { + @Override + public void accept(Integer l) { + System.out.println("onNext --------> " + l); + nextCount.incrementAndGet(); + } + }) + .take(4) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + r.subscribe(new Consumer() { + @Override + public void accept(Integer l) { + receivedCount.incrementAndGet(); + } + }); + + System.out.println("onNext: " + nextCount.get()); + + assertEquals(4, receivedCount.get()); + assertEquals(4, receivedCount.get()); + } + + @Test + public void testRepeat() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger unsubscribeCount = new AtomicInteger(); + Flowable r = Flowable.interval(0, 1, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription s) { + System.out.println("******************************* Subscribe received"); + // when we are subscribed + subscribeCount.incrementAndGet(); + } + }) + .doOnCancel(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + unsubscribeCount.incrementAndGet(); + } + }) + .publish().refCount(); + + for (int i = 0; i < 10; i++) { + TestSubscriber ts1 = new TestSubscriber(); + TestSubscriber ts2 = new TestSubscriber(); + r.subscribe(ts1); + r.subscribe(ts2); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + ts1.dispose(); + ts2.dispose(); + ts1.assertNoErrors(); + ts2.assertNoErrors(); + assertTrue(ts1.valueCount()> 0); + assertTrue(ts2.valueCount()> 0); + } + + assertEquals(10, subscribeCount.get()); + assertEquals(10, unsubscribeCount.get()); + } + + @Test + public void testConnectUnsubscribe() throws InterruptedException { + final CountDownLatch unsubscribeLatch = new CountDownLatch(1); + final CountDownLatch subscribeLatch = new CountDownLatch(1); + + Flowable f = synchronousInterval() + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription s) { + System.out.println("******************************* Subscribe received"); + // when we are subscribed + subscribeLatch.countDown(); + } + }) + .doOnCancel(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + unsubscribeLatch.countDown(); + } + }); + + TestSubscriber s = new TestSubscriber(); + f.publish().refCount().subscribeOn(Schedulers.newThread()).subscribe(s); + System.out.println("send unsubscribe"); + // wait until connected + subscribeLatch.await(); + // now unsubscribe + s.dispose(); + System.out.println("DONE sending unsubscribe ... now waiting"); + if (!unsubscribeLatch.await(3000, TimeUnit.MILLISECONDS)) { + System.out.println("Errors: " + s.errors()); + if (s.errors().size()> 0) { + s.errors().get(0).printStackTrace(); + } + fail("timed out waiting for unsubscribe"); + } + s.assertNoErrors(); + } + + @Test + public void testConnectUnsubscribeRaceConditionLoop() throws InterruptedException { + for (int i = 0; i < 100; i++) { + testConnectUnsubscribeRaceCondition(); + } + } + + @Test + public void testConnectUnsubscribeRaceCondition() throws InterruptedException { + final AtomicInteger subUnsubCount = new AtomicInteger(); + Flowable f = synchronousInterval() + .doOnCancel(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + subUnsubCount.decrementAndGet(); + } + }) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription s) { + System.out.println("******************************* SUBSCRIBE received"); + subUnsubCount.incrementAndGet(); + } + }); + + TestSubscriber s = new TestSubscriber(); + + f.publish().refCount().subscribeOn(Schedulers.computation()).subscribe(s); + System.out.println("send unsubscribe"); + // now immediately unsubscribe while subscribeOn is racing to subscribe + s.dispose(); + // this generally will mean it won't even subscribe as it is already unsubscribed by the time connect() gets scheduled + // give time to the counter to update + Thread.sleep(10); + // either we subscribed and then unsubscribed, or we didn't ever even subscribe + assertEquals(0, subUnsubCount.get()); + + System.out.println("DONE sending unsubscribe ... now waiting"); + System.out.println("Errors: " + s.errors()); + if (s.errors().size()> 0) { + s.errors().get(0).printStackTrace(); + } + s.assertNoErrors(); + } + + private Flowable synchronousInterval() { + return Flowable.unsafeCreate(new Publisher() { + @Override + public void subscribe(Subscriber subscriber) { + final AtomicBoolean cancel = new AtomicBoolean(); + subscriber.onSubscribe(new Subscription() { + @Override + public void request(long n) { + + } + + @Override + public void cancel() { + cancel.set(true); + } + + }); + for (;;) { + if (cancel.get()) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + subscriber.onNext(1L); + } + } + }); + } + + @Test + public void onlyFirstShouldSubscribeAndLastUnsubscribe() { + final AtomicInteger subscriptionCount = new AtomicInteger(); + final AtomicInteger unsubscriptionCount = new AtomicInteger(); + Flowable flowable = Flowable.unsafeCreate(new Publisher() { + @Override + public void subscribe(Subscriber subscriber) { + subscriptionCount.incrementAndGet(); + subscriber.onSubscribe(new Subscription() { + @Override + public void request(long n) { + + } + + @Override + public void cancel() { + unsubscriptionCount.incrementAndGet(); + } + }); + } + }); + Flowable refCounted = flowable.publish().refCount(); + + Disposable first = refCounted.subscribe(); + assertEquals(1, subscriptionCount.get()); + + Disposable second = refCounted.subscribe(); + assertEquals(1, subscriptionCount.get()); + + first.dispose(); + assertEquals(0, unsubscriptionCount.get()); + + second.dispose(); + assertEquals(1, unsubscriptionCount.get()); + } + + @Test + public void testRefCount() { + TestScheduler s = new TestScheduler(); + Flowable interval = Flowable.interval(100, TimeUnit.MILLISECONDS, s).publish().refCount(); + + // subscribe list1 + final List list1 = new ArrayList(); + Disposable d1 = interval.subscribe(new Consumer() { + @Override + public void accept(Long t1) { + list1.add(t1); + } + }); + + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list1.size()); + assertEquals(0L, list1.get(0).longValue()); + assertEquals(1L, list1.get(1).longValue()); + + // subscribe list2 + final List list2 = new ArrayList(); + Disposable d2 = interval.subscribe(new Consumer() { + @Override + public void accept(Long t1) { + list2.add(t1); + } + }); + + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should have 5 items + assertEquals(5, list1.size()); + assertEquals(2L, list1.get(2).longValue()); + assertEquals(3L, list1.get(3).longValue()); + assertEquals(4L, list1.get(4).longValue()); + + // list 2 should only have 3 items + assertEquals(3, list2.size()); + assertEquals(2L, list2.get(0).longValue()); + assertEquals(3L, list2.get(1).longValue()); + assertEquals(4L, list2.get(2).longValue()); + + // unsubscribe list1 + d1.dispose(); + + // advance further + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should still have 5 items + assertEquals(5, list1.size()); + + // list 2 should have 6 items + assertEquals(6, list2.size()); + assertEquals(5L, list2.get(3).longValue()); + assertEquals(6L, list2.get(4).longValue()); + assertEquals(7L, list2.get(5).longValue()); + + // unsubscribe list2 + d2.dispose(); + + // advance further + s.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + // subscribing a new one should start over because the source should have been unsubscribed + // subscribe list3 + final List list3 = new ArrayList(); + interval.subscribe(new Consumer() { + @Override + public void accept(Long t1) { + list3.add(t1); + } + }); + + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list3.size()); + assertEquals(0L, list3.get(0).longValue()); + assertEquals(1L, list3.get(1).longValue()); + + } + + @Test + public void testAlreadyUnsubscribedClient() { + Subscriber done = CancelledSubscriber.INSTANCE; + + Subscriber subscriber = TestHelper.mockSubscriber(); + + Flowable result = Flowable.just(1).publish().refCount(); + + result.subscribe(done); + + result.subscribe(subscriber); + + verify(subscriber).onNext(1); + verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + } + + @Test + public void testAlreadyUnsubscribedInterleavesWithClient() { + ReplayProcessor source = ReplayProcessor.create(); + + Subscriber done = CancelledSubscriber.INSTANCE; + + Subscriber subscriber = TestHelper.mockSubscriber(); + InOrder inOrder = inOrder(subscriber); + + Flowable result = source.publish().refCount(); + + result.subscribe(subscriber); + + source.onNext(1); + + result.subscribe(done); + + source.onNext(2); + source.onComplete(); + + inOrder.verify(subscriber).onNext(1); + inOrder.verify(subscriber).onNext(2); + inOrder.verify(subscriber).onComplete(); + verify(subscriber, never()).onError(any(Throwable.class)); + } + + @Test + public void testConnectDisconnectConnectAndSubjectState() { + Flowable f1 = Flowable.just(10); + Flowable f2 = Flowable.just(20); + Flowable combined = Flowable.combineLatest(f1, f2, new BiFunction() { + @Override + public Integer apply(Integer t1, Integer t2) { + return t1 + t2; + } + }) + .publish().refCount(); + + TestSubscriber ts1 = new TestSubscriber(); + TestSubscriber ts2 = new TestSubscriber(); + + combined.subscribe(ts1); + combined.subscribe(ts2); + + ts1.assertTerminated(); + ts1.assertNoErrors(); + ts1.assertValue(30); + + ts2.assertTerminated(); + ts2.assertNoErrors(); + ts2.assertValue(30); + } + + @Test(timeout = 10000) + public void testUpstreamErrorAllowsRetry() throws InterruptedException { + List errors = TestHelper.trackPluginErrors(); + try { + final AtomicInteger intervalSubscribed = new AtomicInteger(); + Flowable interval = + Flowable.interval(200, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription s) { + System.out.println("Subscribing to interval " + intervalSubscribed.incrementAndGet()); + } + } + ) + .flatMap(new Function>() { + @Override + public Publisher apply(Long t1) { + return Flowable.defer(new Callable>() { + @Override + public Publisher call() { + return Flowable.error(new TestException("Some exception")); + } + }); + } + }) + .onErrorResumeNext(new Function>() { + @Override + public Publisher apply(Throwable t1) { + return Flowable.error(t1); + } + }) + .publish() + .refCount(); + + interval + .doOnError(new Consumer() { + @Override + public void accept(Throwable t1) { + System.out.println("Subscriber 1 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer() { + @Override + public void accept(String t1) { + System.out.println("Subscriber 1: " + t1); + } + }); + Thread.sleep(100); + interval + .doOnError(new Consumer() { + @Override + public void accept(Throwable t1) { + System.out.println("Subscriber 2 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer() { + @Override + public void accept(String t1) { + System.out.println("Subscriber 2: " + t1); + } + }); + + Thread.sleep(1300); + + System.out.println(intervalSubscribed.get()); + assertEquals(6, intervalSubscribed.get()); + + TestHelper.assertError(errors, 0, OnErrorNotImplementedException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + private enum CancelledSubscriber implements FlowableSubscriber { + INSTANCE; + + @Override public void onSubscribe(Subscription s) { + s.cancel(); + } + + @Override public void onNext(Integer o) { + } + + @Override public void onError(Throwable t) { + } + + @Override public void onComplete() { + } + } + + @Test + public void disposed() { + TestHelper.checkDisposed(Flowable.just(1).publish().refCount()); + } + + @Test + public void noOpConnect() { + final int[] calls = { 0 }; + Flowable f = new ConnectableFlowable() { + @Override + public void connect(Consumer connection) { + calls[0]++; + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + } + }.refCount(); + + f.test(); + f.test(); + + assertEquals(1, calls[0]); + } + + Flowable source; + + @Test + public void replayNoLeak() throws Exception { + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Flowable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }) + .replay(1) + .refCount(); + + source.subscribe(); + + Thread.sleep(100); + 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 { + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Flowable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Flowable.never()) + .replay(1) + .refCount(); + + Disposable d1 = source.subscribe(); + Disposable d2 = source.subscribe(); + + d1.dispose(); + d2.dispose(); + + d1 = null; + d2 = null; + + Thread.sleep(100); + 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; + + ExceptionData(Object data) { + this.data = data; + } + } + + @Test + public void publishNoLeak() throws Exception { + Thread.sleep(100); + System.gc(); + Thread.sleep(100); + + long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + source = Flowable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + throw new ExceptionData(new byte[100 * 1000 * 1000]); + } + }) + .publish() + .refCount(); + + source.subscribe(Functions.emptyConsumer(), Functions.emptyConsumer()); + + Thread.sleep(100); + System.gc(); + Thread.sleep(200); + + 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 = Flowable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return new byte[100 * 1000 * 1000]; + } + }).concatWith(Flowable.never()) + .publish() + .refCount(); + + Disposable d1 = source.test(); + Disposable d2 = source.test(); + + d1.dispose(); + d2.dispose(); + + d1 = null; + d2 = 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() { + ConnectableFlowable cf = Flowable.just(1) + .replay(); + + if (cf instanceof Disposable) { + assertTrue(((Disposable)cf).isDisposed()); + + Disposable connection = cf.connect(); + + assertFalse(((Disposable)cf).isDisposed()); + + connection.dispose(); + + assertTrue(((Disposable)cf).isDisposed()); + } + } + + static final class BadFlowableSubscribe extends ConnectableFlowable { + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + throw new TestException("subscribeActual"); + } + } + + static final class BadFlowableDispose extends ConnectableFlowable implements Disposable { + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + } + } + + static final class BadFlowableConnect extends ConnectableFlowable { + + @Override + public void connect(Consumer connection) { + throw new TestException("connect"); + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + } + } + + @Test + public void badSourceSubscribe() { + List errors = TestHelper.trackPluginErrors(); + try { + BadFlowableSubscribe bo = new BadFlowableSubscribe(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void badSourceDispose() { + BadFlowableDispose bf = new BadFlowableDispose(); + + try { + bf.refCount() + .test() + .cancel(); + fail("Should have thrown"); + } catch (TestException expected) { + } + } + + @Test + public void badSourceConnect() { + List errors = TestHelper.trackPluginErrors(); + try { + BadFlowableConnect bf = new BadFlowableConnect(); + + try { + bf.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + static final class BadFlowableSubscribe2 extends ConnectableFlowable { + + int count; + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + if (++count == 1) { + subscriber.onSubscribe(new BooleanSubscription()); + } else { + throw new TestException("subscribeActual"); + } + } + } + + @Test + public void badSourceSubscribe2() { + List errors = TestHelper.trackPluginErrors(); + try { + BadFlowableSubscribe2 bf = new BadFlowableSubscribe2(); + + Flowable f = bf.refCount(); + f.test(); + try { + f.test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + static final class BadFlowableConnect2 extends ConnectableFlowable + implements Disposable { + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + } + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void badSourceCompleteDisconnect() { + List errors = TestHelper.trackPluginErrors(); + try { + BadFlowableConnect2 bf = new BadFlowableConnect2(); + + try { + bf.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test(timeout = 7500) + public void blockingSourceAsnycCancel() throws Exception { + BehaviorProcessor bp = BehaviorProcessor.createDefault(1); + + Flowable f = bp + .replay(1) + .refCount(); + + f.subscribe(); + + final AtomicBoolean interrupted = new AtomicBoolean(); + + f.switchMap(new Function>() { + @Override + public Publisher apply(Integer v) throws Exception { + return Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter emitter) throws Exception { + while (!emitter.isCancelled()) { + Thread.sleep(100); + } + interrupted.set(true); + } + }, BackpressureStrategy.MISSING); + } + }) + .takeUntil(Flowable.timer(500, TimeUnit.MILLISECONDS)) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + + assertTrue(interrupted.get()); + } + + @Test + public void byCount() { + final int[] subscriptions = { 0 }; + + Flowable source = Flowable.range(1, 5) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription s) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(2); + + for (int i = 0; i < 3; i++) { + TestSubscriber ts1 = source.test(); + + ts1.assertEmpty(); + + TestSubscriber ts2 = source.test(); + + ts1.assertResult(1, 2, 3, 4, 5); + ts2.assertResult(1, 2, 3, 4, 5); + } + + assertEquals(3, subscriptions[0]); + } + + @Test + public void resubscribeBeforeTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishProcessor pp = PublishProcessor.create(); + + Flowable source = pp + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription s) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(500, TimeUnit.MILLISECONDS); + + TestSubscriber ts1 = source.test(0); + + assertEquals(1, subscriptions[0]); + + ts1.cancel(); + + Thread.sleep(100); + + ts1 = source.test(0); + + assertEquals(1, subscriptions[0]); + + Thread.sleep(500); + + assertEquals(1, subscriptions[0]); + + pp.onNext(1); + pp.onNext(2); + pp.onNext(3); + pp.onNext(4); + pp.onNext(5); + pp.onComplete(); + + ts1.requestMore(5) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void letitTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishProcessor pp = PublishProcessor.create(); + + Flowable source = pp + .doOnSubscribe(new Consumer() { + @Override + public void accept(Subscription s) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(1, 100, TimeUnit.MILLISECONDS); + + TestSubscriber ts1 = source.test(0); + + assertEquals(1, subscriptions[0]); + + ts1.cancel(); + + assertTrue(pp.hasSubscribers()); + + Thread.sleep(200); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void error() { + Flowable.error(new IOException()) + .publish() + .refCount(500, TimeUnit.MILLISECONDS) + .test() + .assertFailure(IOException.class); + } + + @Test + public void comeAndGo() { + PublishProcessor pp = PublishProcessor.create(); + + Flowable source = pp + .publish() + .refCount(1); + + TestSubscriber ts1 = source.test(0); + + assertTrue(pp.hasSubscribers()); + + for (int i = 0; i < 3; i++) { + TestSubscriber ts2 = source.test(); + ts1.cancel(); + ts1 = ts2; + } + + ts1.cancel(); + + assertFalse(pp.hasSubscribers()); + } + + @Test + public void unsubscribeSubscribeRace() { + for (int i = 0; i < 1000; i++) { + + final Flowable source = Flowable.range(1, 5) + .replay() + .refCount(1) + ; + + final TestSubscriber ts1 = source.test(0); + + final TestSubscriber ts2 = new TestSubscriber(0); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ts1.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + source.subscribe(ts2); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + ts2.requestMore(6) // FIXME RxJava replay() doesn't issue onComplete without request + .withTag("Round: " + i) + .assertResult(1, 2, 3, 4, 5); + } + } + + static final class BadFlowableDoubleOnX extends ConnectableFlowable + implements Disposable { + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onSubscribe(new BooleanSubscription()); + subscriber.onComplete(); + subscriber.onComplete(); + subscriber.onError(new TestException()); + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void doubleOnX() { + List errors = TestHelper.trackPluginErrors(); + try { + new BadFlowableDoubleOnX() + .refCount() + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXCount() { + List errors = TestHelper.trackPluginErrors(); + try { + new BadFlowableDoubleOnX() + .refCount(1) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXTime() { + List errors = TestHelper.trackPluginErrors(); + try { + new BadFlowableDoubleOnX() + .refCount(5, TimeUnit.SECONDS, Schedulers.single()) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void cancelTerminateStateExclusion() { + FlowableRefCount o = (FlowableRefCount)PublishProcessor.create() + .publish() + .refCount(); + + o.cancel(null); + + RefConnection rc = new RefConnection(o); + o.connection = null; + rc.subscriberCount = 0; + o.timeout(rc); + + rc.subscriberCount = 1; + o.timeout(rc); + + o.connection = rc; + o.timeout(rc); + + rc.subscriberCount = 0; + o.timeout(rc); + + // ------------------- + + rc.subscriberCount = 2; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 2; + rc.connected = true; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = true; + o.connection = rc; + rc.set(null); + o.cancel(rc); + + o.connection = rc; + o.cancel(new RefConnection(o)); + } + + @Test + public void replayRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Flowable flowable = Flowable.just(1).replay(1).refCount(); + + TestSubscriber ts1 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + TestSubscriber ts2 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + ts1 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + + ts2 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + } + } + + static final class TestConnectableFlowable extends ConnectableFlowable + implements Disposable { + + volatile boolean disposed; + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void connect(Consumer connection) { + // not relevant + } + + @Override + protected void subscribeActual(Subscriber subscriber) { + // not relevant + } + } + + @Test + public void timeoutDisposesSource() { + FlowableRefCount o = (FlowableRefCount)new TestConnectableFlowable().refCount(); + + RefConnection rc = new RefConnection(o); + o.connection = rc; + + o.timeout(rc); + + assertTrue(((Disposable)o.source).isDisposed()); + } + + @Test + public void disconnectBeforeConnect() { + BehaviorProcessor processor = BehaviorProcessor.create(); + + Flowable flowable = processor + .replay(1) + .refCount(); + + flowable.takeUntil(Flowable.just(1)).test(); + + processor.onNext(2); + + flowable.take(1).test().assertResult(2); + } + + @Test + public void publishRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Flowable flowable = Flowable.just(1).publish().refCount(); + + TestSubscriber subscriber1 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + TestSubscriber subscriber2 = flowable + .subscribeOn(Schedulers.io()) + .test(); + + subscriber1 + .withTag("subscriber1 " + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + + subscriber2 + .withTag("subscriber2 " + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + } + } + + @Test + public void upstreamTerminationTriggersAnotherCancel() throws Exception { + ReplayProcessor rp = ReplayProcessor.create(); + rp.onNext(1); + rp.onComplete(); + + Flowable shared = rp.share(); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java index 673a0f4add..c032e61da5 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRefCountTest.java @@ -23,7 +23,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; import org.reactivestreams.*; @@ -43,6 +43,28 @@ public class FlowableRefCountTest { + // This will undo the workaround so that the plain ObservablePublish is still + // tested. + @Before + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void before() { + RxJavaPlugins.setOnConnectableFlowableAssembly(new Function() { + @Override + public ConnectableFlowable apply(ConnectableFlowable co) throws Exception { + if (co instanceof FlowablePublishAlt) { + FlowablePublishAlt fpa = (FlowablePublishAlt) co; + return FlowablePublish.create(Flowable.fromPublisher(fpa.source()), fpa.publishBufferSize()); + } + return co; + } + }); + } + + @After + public void after() { + RxJavaPlugins.setOnConnectableFlowableAssembly(null); + } + @Test public void testRefCountAsync() { final AtomicInteger subscribeCount = new AtomicInteger(); @@ -653,6 +675,7 @@ protected void subscribeActual(Subscriber subscriber) { @Test public void replayNoLeak() throws Exception { + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -669,6 +692,7 @@ public Object call() throws Exception { source.subscribe(); + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -680,6 +704,7 @@ public Object call() throws Exception { @Test public void replayNoLeak2() throws Exception { + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -703,6 +728,7 @@ public Object call() throws Exception { d1 = null; d2 = null; + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -724,6 +750,7 @@ static final class ExceptionData extends Exception { @Test public void publishNoLeak() throws Exception { + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -740,6 +767,7 @@ public Object call() throws Exception { source.subscribe(Functions.emptyConsumer(), Functions.emptyConsumer()); + Thread.sleep(100); System.gc(); Thread.sleep(100); @@ -1409,4 +1437,23 @@ public void disconnectBeforeConnect() { flowable.take(1).test().assertResult(2); } + + @Test + public void upstreamTerminationTriggersAnotherCancel() throws Exception { + ReplayProcessor rp = ReplayProcessor.create(); + rp.onNext(1); + rp.onComplete(); + + Flowable shared = rp.share(); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java index fd5061d9ed..46d240b620 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRepeatTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java index b3bea718bd..56daf0c5e5 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableReplayTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.management.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java index a47a5d6eca..d0c73867e6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java index de31e283a4..c24e4f5f7d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableRetryWithPredicateTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java index 74cbcc24c2..1ea39f76ce 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSampleTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableScanTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableScanTest.java index 7e761ca53f..1b2b9ac4ca 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableScanTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableScanTest.java @@ -24,7 +24,6 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.*; import io.reactivex.flowable.*; import io.reactivex.flowable.FlowableEventStream.Event; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java index fb65114e42..b40a40bb56 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSequenceEqualTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java index 62f662804f..71085466cb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSingleTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimedTest.java index d24a84b4b6..31c8294a64 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipLastTimedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipWhileTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipWhileTest.java index b9574cd8ef..0ac4a1369e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipWhileTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSkipWhileTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import org.junit.Test; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java index dc5f2a8313..794e43e273 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableSwitchTest.java @@ -19,7 +19,7 @@ import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; @@ -1202,4 +1202,102 @@ public Object apply(Integer w) throws Exception { .assertNoErrors() .assertComplete(); } + + @Test + public void switchMapFusedIterable() { + Flowable.range(1, 2) + .switchMap(new Function>() { + @Override + public Publisher apply(Integer v) + throws Exception { + return Flowable.fromIterable(Arrays.asList(v * 10)); + } + }) + .test() + .assertResult(10, 20); + } + + @Test + public void switchMapHiddenIterable() { + Flowable.range(1, 2) + .switchMap(new Function>() { + @Override + public Publisher apply(Integer v) + throws Exception { + return Flowable.fromIterable(Arrays.asList(v * 10)).hide(); + } + }) + .test() + .assertResult(10, 20); + } + + @Test + public void cancellationShouldTriggerInnerCancellationRace() throws Throwable { + final AtomicInteger outer = new AtomicInteger(); + final AtomicInteger inner = new AtomicInteger(); + + int n = 10000; + for (int i = 0; i < n; i++) { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter it) + throws Exception { + it.onNext(0); + } + }, BackpressureStrategy.MISSING) + .switchMap(new Function>() { + @Override + public Publisher apply(Integer v) + throws Exception { + return createFlowable(inner); + } + }) + .observeOn(Schedulers.computation()) + .doFinally(new Action() { + @Override + public void run() throws Exception { + outer.incrementAndGet(); + } + }) + .take(1) + .blockingSubscribe(Functions.emptyConsumer(), new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + e.printStackTrace(); + } + }); + } + + Thread.sleep(100); + assertEquals(inner.get(), outer.get()); + assertEquals(n, inner.get()); + } + + Flowable createFlowable(final AtomicInteger inner) { + return Flowable.unsafeCreate(new Publisher() { + @Override + public void subscribe(Subscriber s) { + final SerializedSubscriber it = new SerializedSubscriber(s); + it.onSubscribe(new BooleanSubscription()); + Schedulers.io().scheduleDirect(new Runnable() { + @Override + public void run() { + it.onNext(1); + } + }, 0, TimeUnit.MILLISECONDS); + Schedulers.io().scheduleDirect(new Runnable() { + @Override + public void run() { + it.onNext(2); + } + }, 0, TimeUnit.MILLISECONDS); + } + }) + .doFinally(new Action() { + @Override + public void run() throws Exception { + inner.incrementAndGet(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTest.java index b0d1af43c7..3320650881 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTest.java @@ -24,7 +24,6 @@ import org.reactivestreams.Subscriber; import io.reactivex.*; -import io.reactivex.Flowable; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.schedulers.Schedulers; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java index 4864cac7f6..5f2cda82ea 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeLastTimedTest.java @@ -336,4 +336,27 @@ public Publisher apply(Flowable f) throws Exception { public void badRequest() { TestHelper.assertBadRequestReported(PublishProcessor.create().takeLast(1, TimeUnit.SECONDS)); } + + @Test + public void lastWindowIsFixedInTime() { + TimesteppingScheduler scheduler = new TimesteppingScheduler(); + scheduler.stepEnabled = false; + + PublishProcessor pp = PublishProcessor.create(); + + TestSubscriber ts = pp + .takeLast(2, TimeUnit.SECONDS, scheduler) + .test(); + + pp.onNext(1); + pp.onNext(2); + pp.onNext(3); + pp.onNext(4); + + scheduler.stepEnabled = true; + + pp.onComplete(); + + ts.assertResult(1, 2, 3, 4); + } } diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java index 91d4d4ea8b..59d049a7c0 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicateTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicateTest.java index acba2b1294..72e08bd1b3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicateTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeUntilPredicateTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeWhileTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeWhileTest.java index 97eac27982..f99b7d1d86 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeWhileTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTakeWhileTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java index 2e5fd020e7..12fc35760e 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableThrottleFirstTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java index 7e4b97c899..5f6aa08fa6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutTests.java @@ -15,7 +15,6 @@ import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutWithSelectorTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutWithSelectorTest.java index 178e015413..d1ba19d32b 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutWithSelectorTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimeoutWithSelectorTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java index e5b9c61de4..b02fea95b3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableTimerTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java index ffa4d0eba3..08dc9c244d 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableToListTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.flowable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java index cd6f22ea56..36112023bb 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableUnsubscribeOnTest.java @@ -73,7 +73,7 @@ public void subscribe(Subscriber t1) { System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertTrue(unsubscribeThread == uiEventLoop.getThread()); + assertSame(unsubscribeThread, uiEventLoop.getThread()); ts.assertValues(1, 2); ts.assertTerminated(); diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithFlowableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithFlowableTest.java index 919ca0e468..ce321df789 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithFlowableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithFlowableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithSizeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithSizeTest.java index b6fb51f6d2..af412d9a02 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithSizeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithSizeTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java index 157faae1a5..56e1a736e6 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWindowWithTimeTest.java @@ -16,7 +16,7 @@ import static org.junit.Assert.*; import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.*; import org.junit.*; @@ -64,17 +64,19 @@ public void subscribe(Subscriber subscriber) { Flowable> windowed = source.window(100, TimeUnit.MILLISECONDS, scheduler, 2); windowed.subscribe(observeWindow(list, lists)); - scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); + scheduler.advanceTimeTo(95, TimeUnit.MILLISECONDS); assertEquals(1, lists.size()); assertEquals(lists.get(0), list("one", "two")); - scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); - assertEquals(2, lists.size()); - assertEquals(lists.get(1), list("three", "four")); + scheduler.advanceTimeTo(195, TimeUnit.MILLISECONDS); + assertEquals(3, lists.size()); + assertTrue(lists.get(1).isEmpty()); + assertEquals(lists.get(2), list("three", "four")); scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); - assertEquals(3, lists.size()); - assertEquals(lists.get(2), list("five")); + assertEquals(5, lists.size()); + assertTrue(lists.get(3).isEmpty()); + assertEquals(lists.get(4), list("five")); } @Test @@ -916,5 +918,244 @@ public void nextWindowMissingBackpressureDrainOnTime() { .assertError(MissingBackpressureException.class) .assertNotComplete(); } -} + @Test + public void exactTimeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Flowable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Flowable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeAndSizeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(100, TimeUnit.MILLISECONDS, 10) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Flowable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeAndSizeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(100, TimeUnit.MILLISECONDS, 10) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Flowable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void skipTimeAndSizeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(90, 100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Flowable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void skipTimeAndSizeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishProcessor pp = PublishProcessor.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + pp.window(90, 100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Flowable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + pp.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + pp.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromTest.java index 070176bdb0..c534ddd990 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableWithLatestFromTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipIterableTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipIterableTest.java index a86de18f52..2ca5481fc3 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipIterableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipTest.java b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipTest.java index ef1223d66a..ee691d93c2 100644 --- a/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipTest.java +++ b/src/test/java/io/reactivex/internal/operators/flowable/FlowableZipTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.flowable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.*; @@ -1895,4 +1894,34 @@ public Integer apply(Integer a, Integer b) throws Exception { ts.assertResult(4); } + + @Test + public void firstErrorPreventsSecondSubscription() { + final AtomicInteger counter = new AtomicInteger(); + + List> flowableList = new ArrayList>(); + flowableList.add(Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) + throws Exception { throw new TestException(); } + }, BackpressureStrategy.MISSING)); + flowableList.add(Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) + throws Exception { counter.getAndIncrement(); } + }, BackpressureStrategy.MISSING)); + + Flowable.zip(flowableList, + new Function() { + @Override + public Object apply(Object[] a) throws Exception { + return a; + } + }) + .test() + .assertFailure(TestException.class) + ; + + assertEquals(0, counter.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java index d442fcc135..0e90e57731 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeDoOnTerminateTest.java @@ -19,8 +19,6 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Action; import io.reactivex.observers.TestObserver; -import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.subjects.PublishSubject; import org.junit.Test; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCallableTest.java index 7a56347a32..44f50947ee 100644 --- a/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/maybe/MaybeFromCallableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.maybe; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableLatestTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableLatestTest.java index cdd95c58f2..27aa27ec2d 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableLatestTest.java @@ -44,13 +44,13 @@ public void testSimple() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } @Test(timeout = 1000) @@ -69,13 +69,13 @@ public void testSameSourceMultipleIterators() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } } @@ -87,7 +87,7 @@ public void testEmpty() { Iterator it = iter.iterator(); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); it.next(); } @@ -166,7 +166,7 @@ public void testFasterSource() { source.onNext(7); source.onComplete(); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } @Test(expected = UnsupportedOperationException.class) diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecentTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecentTest.java index 5a277d6ddd..a109beb2d7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecentTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableMostRecentTest.java @@ -28,7 +28,7 @@ public class BlockingObservableMostRecentTest { @Test public void testMostRecentNull() { - assertEquals(null, Observable.never().blockingMostRecent(null).iterator().next()); + assertNull(Observable.never().blockingMostRecent(null).iterator().next()); } static Iterable mostRecent(Observable source, T initialValue) { @@ -91,12 +91,12 @@ public void testSingleSourceManyIterators() { for (int i = 0; i < 9; i++) { scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(Long.valueOf(i), it.next()); } scheduler.advanceTimeBy(1, TimeUnit.SECONDS); - Assert.assertEquals(false, it.hasNext()); + Assert.assertFalse(it.hasNext()); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableNextTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableNextTest.java index 2b8e670c16..b854c317b2 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableNextTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableNextTest.java @@ -28,7 +28,6 @@ import io.reactivex.exceptions.TestException; import io.reactivex.internal.operators.observable.BlockingObservableNext.NextObserver; import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.processors.BehaviorProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.*; @@ -323,7 +322,7 @@ public void testSingleSourceManyIterators() throws InterruptedException { BlockingObservableNext.NextIterator it = (BlockingObservableNext.NextIterator)iter.iterator(); for (long i = 0; i < 10; i++) { - Assert.assertEquals(true, it.hasNext()); + Assert.assertTrue(it.hasNext()); Assert.assertEquals(j + "th iteration next", Long.valueOf(i), it.next()); } terminal.onNext(1); @@ -332,9 +331,9 @@ public void testSingleSourceManyIterators() throws InterruptedException { @Test public void testSynchronousNext() { - assertEquals(1, BehaviorProcessor.createDefault(1).take(1).blockingSingle().intValue()); - assertEquals(2, BehaviorProcessor.createDefault(2).blockingIterable().iterator().next().intValue()); - assertEquals(3, BehaviorProcessor.createDefault(3).blockingNext().iterator().next().intValue()); + assertEquals(1, BehaviorSubject.createDefault(1).take(1).blockingSingle().intValue()); + assertEquals(2, BehaviorSubject.createDefault(2).blockingIterable().iterator().next().intValue()); + assertEquals(3, BehaviorSubject.createDefault(3).blockingNext().iterator().next().intValue()); } @Test diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToFutureTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToFutureTest.java index f4fa707ee4..632d3dad72 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToFutureTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToFutureTest.java @@ -121,6 +121,6 @@ public void testGetWithEmptyFlowable() throws Throwable { public void testGetWithASingleNullItem() throws Exception { Observable obs = Observable.just((String)null); Future f = obs.toFuture(); - assertEquals(null, f.get()); + assertNull(f.get()); } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToIteratorTest.java b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToIteratorTest.java index af22e83993..324b7c2172 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToIteratorTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/BlockingObservableToIteratorTest.java @@ -16,15 +16,18 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.*; import io.reactivex.Observable; import io.reactivex.ObservableSource; import io.reactivex.Observer; -import io.reactivex.disposables.Disposables; +import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.internal.operators.observable.BlockingObservableIterable.BlockingObservableIterator; +import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; public class BlockingObservableToIteratorTest { @@ -34,16 +37,16 @@ public void testToIterator() { Iterator it = obs.blockingIterable().iterator(); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("one", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("two", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("three", it.next()); - assertEquals(false, it.hasNext()); + assertFalse(it.hasNext()); } @@ -61,10 +64,10 @@ public void subscribe(Observer observer) { Iterator it = obs.blockingIterable().iterator(); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); assertEquals("one", it.next()); - assertEquals(true, it.hasNext()); + assertTrue(it.hasNext()); it.next(); } @@ -119,4 +122,28 @@ public void remove() { BlockingObservableIterator it = new BlockingObservableIterator(128); it.remove(); } + + @Test(expected = NoSuchElementException.class) + public void disposedIteratorHasNextReturns() { + Iterator it = PublishSubject.create() + .blockingIterable().iterator(); + ((Disposable)it).dispose(); + assertFalse(it.hasNext()); + it.next(); + } + + @Test + public void asyncDisposeUnblocks() { + final Iterator it = PublishSubject.create() + .blockingIterable().iterator(); + + Schedulers.single().scheduleDirect(new Runnable() { + @Override + public void run() { + ((Disposable)it).dispose(); + } + }, 1, TimeUnit.SECONDS); + + assertFalse(it.hasNext()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableAllTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableAllTest.java index dc8445068c..e53fd7161c 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableAllTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableAllTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java index 6e2c737f75..a76b8f22e6 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableAmbTest.java @@ -264,6 +264,23 @@ public void singleIterable() { .assertResult(1); } + /** + * Ensures that an ObservableSource implementation can be supplied that doesn't subclass Observable + */ + @Test + public void singleIterableNotSubclassingObservable() { + final ObservableSource s1 = new ObservableSource() { + @Override + public void subscribe (final Observer observer) { + Observable.just(1).subscribe(observer); + } + }; + + Observable.amb(Collections.singletonList(s1)) + .test() + .assertResult(1); + } + @SuppressWarnings("unchecked") @Test public void disposed() { diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableAnyTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableAnyTest.java index 7c66cba3f6..fcdc13fee9 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableAnyTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableAnyTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableBufferTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableBufferTest.java index 43612b228d..16ba2fab6d 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableBufferTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableBufferTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -2136,4 +2135,20 @@ public ObservableSource apply(Observable o) } }); } + + @Test + @SuppressWarnings("unchecked") + public void bufferExactFailingSupplier() { + Observable.empty() + .buffer(1, TimeUnit.SECONDS, Schedulers.computation(), 10, new Callable() { + @Override + public List call() throws Exception { + throw new TestException(); + } + }, false) + .test() + .awaitDone(1, TimeUnit.SECONDS) + .assertFailure(TestException.class) + ; + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableCombineLatestTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableCombineLatestTest.java index 55256bccd8..736a0bd9b5 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableCombineLatestTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableCombineLatestTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -788,6 +787,34 @@ public Object apply(Object[] a) throws Exception { .assertResult("[1, 2]"); } + /** + * Ensures that an ObservableSource implementation can be supplied that doesn't subclass Observable + */ + @Test + public void combineLatestIterableOfSourcesNotSubclassingObservable() { + final ObservableSource s1 = new ObservableSource() { + @Override + public void subscribe (final Observer observer) { + Observable.just(1).subscribe(observer); + } + }; + final ObservableSource s2 = new ObservableSource() { + @Override + public void subscribe (final Observer observer) { + Observable.just(2).subscribe(observer); + } + }; + + Observable.combineLatest(Arrays.asList(s1, s2), new Function() { + @Override + public Object apply(Object[] a) throws Exception { + return Arrays.toString(a); + } + }) + .test() + .assertResult("[1, 2]"); + } + @Test @SuppressWarnings("unchecked") public void combineLatestDelayErrorArrayOfSources() { @@ -1217,4 +1244,5 @@ public Object apply(Object[] a) throws Exception { .awaitDone(5, TimeUnit.SECONDS) .assertFailure(TestException.class, 42); } + } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java index 01bf61ac6e..aa24f68cd9 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDebounceTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; @@ -505,4 +504,14 @@ public void timedError() { .test() .assertFailure(TestException.class); } + + @Test + public void debounceOnEmpty() { + Observable.empty().debounce(new Function>() { + @Override + public ObservableSource apply(Object o) { + return Observable.just(new Object()); + } + }).subscribe(); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDelayTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDelayTest.java index 1088f2abc1..40e396b314 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDelayTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDelayTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDematerializeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDematerializeTest.java index d815a70ea5..d2c5a4ca57 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDematerializeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDematerializeTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctTest.java index 8114d64f9a..67f73d3ede 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChangedTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChangedTest.java index b0ba634354..6bd333e814 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChangedTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDistinctUntilChangedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnEachTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnEachTest.java index 161ae3db9e..a22c969c9b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnEachTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableDoOnEachTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFilterTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFilterTest.java index e855c07820..7ac6418db7 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFilterTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFilterTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import org.junit.Test; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapTest.java index 960722060a..61fbd21c7f 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFlatMapTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; @@ -1085,4 +1084,61 @@ public void accept(Integer v) throws Exception { assertFalse(ps3.hasObservers()); assertFalse(ps4.hasObservers()); } + + @Test + public void mainErrorsInnerCancelled() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + ps1 + .flatMap(Functions.justFunction(ps2)) + .test(); + + ps1.onNext(1); + assertTrue("No subscribers?", ps2.hasObservers()); + + ps1.onError(new TestException()); + + assertFalse("Has subscribers?", ps2.hasObservers()); + } + + @Test + public void innerErrorsMainCancelled() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + ps1 + .flatMap(Functions.justFunction(ps2)) + .test(); + + ps1.onNext(1); + assertTrue("No subscribers?", ps2.hasObservers()); + + ps2.onError(new TestException()); + + assertFalse("Has subscribers?", ps1.hasObservers()); + } + + @Test(timeout = 5000) + public void mixedScalarAsync() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + Observable + .range(0, 20) + .flatMap(new Function>() { + @Override + public ObservableSource apply(Integer integer) throws Exception { + if (integer % 5 != 0) { + return Observable + .just(integer); + } + + return Observable + .just(-integer) + .observeOn(Schedulers.computation()); + } + }, false, 1) + .ignoreElements() + .blockingAwait(); + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCallableTest.java index 542165907f..c10d53db36 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromCallableTest.java @@ -17,7 +17,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromIterableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromIterableTest.java index 3b0a9d4970..5ed25eb269 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableFromIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableFromIterableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupByTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupByTest.java index 42f441bd10..31bb5cf6a0 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupByTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupByTest.java @@ -1364,7 +1364,7 @@ public void accept(String s) { }); } }); - assertEquals(null, key[0]); + assertNull(key[0]); assertEquals(Arrays.asList("a", "b", "c"), values); } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java index 3f902cb4a4..59c5a96332 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableGroupJoinTest.java @@ -16,7 +16,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableJoinTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableJoinTest.java index 8ced523827..00a733959d 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableJoinTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableJoinTest.java @@ -15,7 +15,6 @@ */ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableLastTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableLastTest.java index 6f3ecd39c8..77f02bfe71 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableLastTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.NoSuchElementException; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMapTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMapTest.java index 3d824be862..4d271a4197 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMapTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMapTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletableTest.java index 872509e164..d9a54c916a 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithCompletableTest.java @@ -135,4 +135,40 @@ protected void subscribeActual(Observer observer) { .test() .assertResult(1); } + + @Test + public void cancelOtherOnMainError() { + PublishSubject ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver to = ps.mergeWith(cs).test(); + + assertTrue(ps.hasObservers()); + assertTrue(cs.hasObservers()); + + ps.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", cs.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishSubject ps = PublishSubject.create(); + CompletableSubject cs = CompletableSubject.create(); + + TestObserver to = ps.mergeWith(cs).test(); + + assertTrue(ps.hasObservers()); + assertTrue(cs.hasObservers()); + + cs.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", cs.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybeTest.java index a70e4c2fa8..ee9eb9b576 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithMaybeTest.java @@ -272,4 +272,39 @@ public void onNext(Integer t) { to.assertResult(0, 1, 2, 3, 4); } + @Test + public void cancelOtherOnMainError() { + PublishSubject ps = PublishSubject.create(); + MaybeSubject ms = MaybeSubject.create(); + + TestObserver to = ps.mergeWith(ms).test(); + + assertTrue(ps.hasObservers()); + assertTrue(ms.hasObservers()); + + ps.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", ms.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishSubject ps = PublishSubject.create(); + MaybeSubject ms = MaybeSubject.create(); + + TestObserver to = ps.mergeWith(ms).test(); + + assertTrue(ps.hasObservers()); + assertTrue(ms.hasObservers()); + + ms.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", ms.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingleTest.java index 25ce78d486..0d8fb3432b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingleTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableMergeWithSingleTest.java @@ -263,4 +263,40 @@ public void onNext(Integer t) { to.assertResult(0, 1, 2, 3, 4); } + + @Test + public void cancelOtherOnMainError() { + PublishSubject ps = PublishSubject.create(); + SingleSubject ss = SingleSubject.create(); + + TestObserver to = ps.mergeWith(ss).test(); + + assertTrue(ps.hasObservers()); + assertTrue(ss.hasObservers()); + + ps.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", ss.hasObservers()); + } + + @Test + public void cancelMainOnOtherError() { + PublishSubject ps = PublishSubject.create(); + SingleSubject ss = SingleSubject.create(); + + TestObserver to = ps.mergeWith(ss).test(); + + assertTrue(ps.hasObservers()); + assertTrue(ss.hasObservers()); + + ss.onError(new TestException()); + + to.assertFailure(TestException.class); + + assertFalse("main has observers!", ps.hasObservers()); + assertFalse("other has observers", ss.hasObservers()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableObserveOnTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableObserveOnTest.java index 8d948b2592..48cbe91908 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableObserveOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableObserveOnTest.java @@ -29,7 +29,7 @@ import io.reactivex.Observer; import io.reactivex.annotations.Nullable; import io.reactivex.disposables.*; -import io.reactivex.exceptions.TestException; +import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.fuseable.*; import io.reactivex.internal.operators.flowable.FlowableObserveOnTest.DisposeTrackingScheduler; @@ -814,4 +814,36 @@ public void workerNotDisposedPrematurelyNormalInAsyncOut() { assertEquals(1, s.disposedCount.get()); } + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List errors = TestHelper.trackPluginErrors(); + try { + final UnicastSubject us = UnicastSubject.create(); + + TestObserver to = us.hide() + .observeOn(Schedulers.io()) + .observeOn(Schedulers.single()) + .unsubscribeOn(Schedulers.computation()) + .firstOrError() + .test(); + + for (int i = 0; us.hasObservers() && i < 10000; i++) { + us.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaFunctionTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaFunctionTest.java index b8b3f67f1e..fa48b217f8 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaFunctionTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableOnErrorResumeNextViaFunctionTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishAltTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishAltTest.java new file mode 100644 index 0000000000..b268e5b2ed --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishAltTest.java @@ -0,0 +1,794 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.Test; + +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.TestException; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.HasUpstreamObservableSource; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.*; +import io.reactivex.subjects.PublishSubject; + +public class ObservablePublishAltTest { + + @Test + public void testPublish() throws InterruptedException { + final AtomicInteger counter = new AtomicInteger(); + ConnectableObservable o = Observable.unsafeCreate(new ObservableSource() { + + @Override + public void subscribe(final Observer observer) { + observer.onSubscribe(Disposables.empty()); + new Thread(new Runnable() { + + @Override + public void run() { + counter.incrementAndGet(); + observer.onNext("one"); + observer.onComplete(); + } + }).start(); + } + }).publish(); + + final CountDownLatch latch = new CountDownLatch(2); + + // subscribe once + o.subscribe(new Consumer() { + + @Override + public void accept(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + // subscribe again + o.subscribe(new Consumer() { + + @Override + public void accept(String v) { + assertEquals("one", v); + latch.countDown(); + } + }); + + Disposable connection = o.connect(); + try { + if (!latch.await(1000, TimeUnit.MILLISECONDS)) { + fail("subscriptions did not receive values"); + } + assertEquals(1, counter.get()); + } finally { + connection.dispose(); + } + } + + @Test + public void testBackpressureFastSlow() { + ConnectableObservable is = Observable.range(1, Flowable.bufferSize() * 2).publish(); + Observable fast = is.observeOn(Schedulers.computation()) + .doOnComplete(new Action() { + @Override + public void run() { + System.out.println("^^^^^^^^^^^^^ completed FAST"); + } + }); + + Observable slow = is.observeOn(Schedulers.computation()).map(new Function() { + int c; + + @Override + public Integer apply(Integer i) { + if (c == 0) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + } + c++; + return i; + } + + }).doOnComplete(new Action() { + + @Override + public void run() { + System.out.println("^^^^^^^^^^^^^ completed SLOW"); + } + + }); + + TestObserver to = new TestObserver(); + Observable.merge(fast, slow).subscribe(to); + is.connect(); + to.awaitTerminalEvent(); + to.assertNoErrors(); + assertEquals(Flowable.bufferSize() * 4, to.valueCount()); + } + + // use case from https://github.com/ReactiveX/RxJava/issues/1732 + @Test + public void testTakeUntilWithPublishedStreamUsingSelector() { + final AtomicInteger emitted = new AtomicInteger(); + Observable xs = Observable.range(0, Flowable.bufferSize() * 2).doOnNext(new Consumer() { + + @Override + public void accept(Integer t1) { + emitted.incrementAndGet(); + } + + }); + TestObserver to = new TestObserver(); + xs.publish(new Function, Observable>() { + + @Override + public Observable apply(Observable xs) { + return xs.takeUntil(xs.skipWhile(new Predicate() { + + @Override + public boolean test(Integer i) { + return i <= 3; + } + + })); + } + + }).subscribe(to); + to.awaitTerminalEvent(); + to.assertNoErrors(); + to.assertValues(0, 1, 2, 3); + assertEquals(5, emitted.get()); + System.out.println(to.values()); + } + + // use case from https://github.com/ReactiveX/RxJava/issues/1732 + @Test + public void testTakeUntilWithPublishedStream() { + Observable xs = Observable.range(0, Flowable.bufferSize() * 2); + TestObserver to = new TestObserver(); + ConnectableObservable xsp = xs.publish(); + xsp.takeUntil(xsp.skipWhile(new Predicate() { + + @Override + public boolean test(Integer i) { + return i <= 3; + } + + })).subscribe(to); + xsp.connect(); + System.out.println(to.values()); + } + + @Test(timeout = 10000) + public void testBackpressureTwoConsumers() { + final AtomicInteger sourceEmission = new AtomicInteger(); + final AtomicBoolean sourceUnsubscribed = new AtomicBoolean(); + final Observable source = Observable.range(1, 100) + .doOnNext(new Consumer() { + @Override + public void accept(Integer t1) { + sourceEmission.incrementAndGet(); + } + }) + .doOnDispose(new Action() { + @Override + public void run() { + sourceUnsubscribed.set(true); + } + }).share(); + ; + + final AtomicBoolean child1Unsubscribed = new AtomicBoolean(); + final AtomicBoolean child2Unsubscribed = new AtomicBoolean(); + + final TestObserver to2 = new TestObserver(); + + final TestObserver to1 = new TestObserver() { + @Override + public void onNext(Integer t) { + if (valueCount() == 2) { + source.doOnDispose(new Action() { + @Override + public void run() { + child2Unsubscribed.set(true); + } + }).take(5).subscribe(to2); + } + super.onNext(t); + } + }; + + source.doOnDispose(new Action() { + @Override + public void run() { + child1Unsubscribed.set(true); + } + }).take(5) + .subscribe(to1); + + to1.awaitTerminalEvent(); + to2.awaitTerminalEvent(); + + to1.assertNoErrors(); + to2.assertNoErrors(); + + assertTrue(sourceUnsubscribed.get()); + assertTrue(child1Unsubscribed.get()); + assertTrue(child2Unsubscribed.get()); + + to1.assertValues(1, 2, 3, 4, 5); + to2.assertValues(4, 5, 6, 7, 8); + + assertEquals(8, sourceEmission.get()); + } + + @Test + public void testConnectWithNoSubscriber() { + TestScheduler scheduler = new TestScheduler(); + ConnectableObservable co = Observable.interval(10, 10, TimeUnit.MILLISECONDS, scheduler).take(3).publish(); + co.connect(); + // Emit 0 + scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS); + TestObserver to = new TestObserver(); + co.subscribe(to); + // Emit 1 and 2 + scheduler.advanceTimeBy(50, TimeUnit.MILLISECONDS); + to.assertValues(1L, 2L); + to.assertNoErrors(); + to.assertTerminated(); + } + + @Test + public void testSubscribeAfterDisconnectThenConnect() { + ConnectableObservable source = Observable.just(1).publish(); + + TestObserver to1 = new TestObserver(); + + source.subscribe(to1); + + Disposable connection = source.connect(); + + to1.assertValue(1); + to1.assertNoErrors(); + to1.assertTerminated(); + + TestObserver to2 = new TestObserver(); + + source.subscribe(to2); + + Disposable connection2 = source.connect(); + + to2.assertValue(1); + to2.assertNoErrors(); + to2.assertTerminated(); + + System.out.println(connection); + System.out.println(connection2); + } + + @Test + public void testNoSubscriberRetentionOnCompleted() { + ObservablePublish source = (ObservablePublish)Observable.just(1).publish(); + + TestObserver to1 = new TestObserver(); + + source.subscribe(to1); + + to1.assertNoValues(); + to1.assertNoErrors(); + to1.assertNotComplete(); + + source.connect(); + + to1.assertValue(1); + to1.assertNoErrors(); + to1.assertTerminated(); + + assertNull(source.current.get()); + } + + @Test + public void testNonNullConnection() { + ConnectableObservable source = Observable.never().publish(); + + assertNotNull(source.connect()); + assertNotNull(source.connect()); + } + + @Test + public void testNoDisconnectSomeoneElse() { + ConnectableObservable source = Observable.never().publish(); + + Disposable connection1 = source.connect(); + Disposable connection2 = source.connect(); + + connection1.dispose(); + + Disposable connection3 = source.connect(); + + connection2.dispose(); + + assertTrue(checkPublishDisposed(connection1)); + assertTrue(checkPublishDisposed(connection2)); + assertFalse(checkPublishDisposed(connection3)); + } + + @SuppressWarnings("unchecked") + static boolean checkPublishDisposed(Disposable d) { + return ((ObservablePublish.PublishObserver)d).isDisposed(); + } + + @Test + public void testConnectIsIdempotent() { + final AtomicInteger calls = new AtomicInteger(); + Observable source = Observable.unsafeCreate(new ObservableSource() { + @Override + public void subscribe(Observer t) { + t.onSubscribe(Disposables.empty()); + calls.getAndIncrement(); + } + }); + + ConnectableObservable conn = source.publish(); + + assertEquals(0, calls.get()); + + conn.connect(); + conn.connect(); + + assertEquals(1, calls.get()); + + conn.connect().dispose(); + + conn.connect(); + conn.connect(); + + assertEquals(2, calls.get()); + } + + @Test + public void testObserveOn() { + ConnectableObservable co = Observable.range(0, 1000).publish(); + Observable obs = co.observeOn(Schedulers.computation()); + for (int i = 0; i < 1000; i++) { + for (int j = 1; j < 6; j++) { + List> tos = new ArrayList>(); + for (int k = 1; k < j; k++) { + TestObserver to = new TestObserver(); + tos.add(to); + obs.subscribe(to); + } + + Disposable connection = co.connect(); + + for (TestObserver to : tos) { + to.awaitTerminalEvent(2, TimeUnit.SECONDS); + to.assertTerminated(); + to.assertNoErrors(); + assertEquals(1000, to.valueCount()); + } + connection.dispose(); + } + } + } + + @Test + public void preNextConnect() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableObservable co = Observable.empty().publish(); + + co.connect(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + co.test(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void connectRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableObservable co = Observable.empty().publish(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + co.connect(); + } + }; + + TestHelper.race(r1, r1); + } + } + + @Test + public void selectorCrash() { + Observable.just(1).publish(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable v) throws Exception { + throw new TestException(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void source() { + Observable o = Observable.never(); + + assertSame(o, (((HasUpstreamObservableSource)o.publish()).source())); + } + + @Test + public void connectThrows() { + ConnectableObservable co = Observable.empty().publish(); + try { + co.connect(new Consumer() { + @Override + public void accept(Disposable d) throws Exception { + throw new TestException(); + } + }); + } catch (TestException ex) { + // expected + } + } + + @Test + public void addRemoveRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final ConnectableObservable co = Observable.empty().publish(); + + final TestObserver to = co.test(); + + final TestObserver to2 = new TestObserver(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + co.subscribe(to2); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void disposeOnArrival() { + ConnectableObservable co = Observable.empty().publish(); + + co.test(true).assertEmpty(); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(Observable.never().publish()); + + TestHelper.checkDisposed(Observable.never().publish(Functions.identity())); + } + + @Test + public void empty() { + ConnectableObservable co = Observable.empty().publish(); + + co.connect(); + } + + @Test + public void take() { + ConnectableObservable co = Observable.range(1, 2).publish(); + + TestObserver to = co.take(1).test(); + + co.connect(); + + to.assertResult(1); + } + + @Test + public void just() { + final PublishSubject ps = PublishSubject.create(); + + ConnectableObservable co = ps.publish(); + + TestObserver to = new TestObserver() { + @Override + public void onNext(Integer t) { + super.onNext(t); + ps.onComplete(); + } + }; + + co.subscribe(to); + co.connect(); + + ps.onNext(1); + + to.assertResult(1); + } + + @Test + public void nextCancelRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final PublishSubject ps = PublishSubject.create(); + + final ConnectableObservable co = ps.publish(); + + final TestObserver to = co.test(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(1); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + to.cancel(); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void badSource() { + List errors = TestHelper.trackPluginErrors(); + try { + new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposables.empty()); + observer.onNext(1); + observer.onComplete(); + observer.onNext(2); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .publish() + .autoConnect() + .test() + .assertResult(1); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void noErrorLoss() { + List errors = TestHelper.trackPluginErrors(); + try { + ConnectableObservable co = Observable.error(new TestException()).publish(); + + co.connect(); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void subscribeDisconnectRace() { + for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { + + final PublishSubject ps = PublishSubject.create(); + + final ConnectableObservable co = ps.publish(); + + final Disposable d = co.connect(); + final TestObserver to = new TestObserver(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + d.dispose(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + co.subscribe(to); + } + }; + + TestHelper.race(r1, r2); + } + } + + @Test + public void selectorDisconnectsIndependentSource() { + PublishSubject ps = PublishSubject.create(); + + ps.publish(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable v) throws Exception { + return Observable.range(1, 2); + } + }) + .test() + .assertResult(1, 2); + + assertFalse(ps.hasObservers()); + } + + @Test(timeout = 5000) + public void selectorLatecommer() { + Observable.range(1, 5) + .publish(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable v) throws Exception { + return v.concatWith(v); + } + }) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void mainError() { + Observable.error(new TestException()) + .publish(Functions.identity()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void selectorInnerError() { + PublishSubject ps = PublishSubject.create(); + + ps.publish(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable v) throws Exception { + return Observable.error(new TestException()); + } + }) + .test() + .assertFailure(TestException.class); + + assertFalse(ps.hasObservers()); + } + + @Test + public void delayedUpstreamOnSubscribe() { + final Observer[] sub = { null }; + + new Observable() { + @Override + protected void subscribeActual(Observer observer) { + sub[0] = observer; + } + } + .publish() + .connect() + .dispose(); + + Disposable bs = Disposables.empty(); + + sub[0].onSubscribe(bs); + + assertTrue(bs.isDisposed()); + } + + @Test + public void doubleOnSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(new Function() { + @Override + public ObservableSource apply(final Observable o) + throws Exception { + return Observable.never().publish(new Function, ObservableSource>() { + @Override + public ObservableSource apply(Observable v) + throws Exception { + return o; + } + }); + } + } + ); + } + + @Test + public void disposedUpfront() { + ConnectableObservable co = Observable.just(1) + .concatWith(Observable.never()) + .publish(); + + TestObserver to1 = co.test(); + + TestObserver to2 = co.test(true); + + co.connect(); + + to1.assertValuesOnly(1); + + to2.assertEmpty(); + + ((ObservablePublish)co).current.get().remove(null); + } + + @Test + public void altConnectCrash() { + try { + new ObservablePublishAlt(Observable.empty()) + .connect(new Consumer() { + @Override + public void accept(Disposable t) throws Exception { + throw new TestException(); + } + }); + fail("Should have thrown"); + } catch (TestException expected) { + // expected + } + } + + @Test + public void altConnectRace() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + final ConnectableObservable co = + new ObservablePublishAlt(Observable.never()); + + Runnable r = new Runnable() { + @Override + public void run() { + co.connect(); + } + }; + + TestHelper.race(r, r); + } + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java index 2f2a0e677e..7534f07346 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservablePublishTest.java @@ -19,7 +19,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import io.reactivex.*; import io.reactivex.Observable; @@ -37,6 +37,27 @@ public class ObservablePublishTest { + // This will undo the workaround so that the plain ObservablePublish is still + // tested. + @Before + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void before() { + RxJavaPlugins.setOnConnectableObservableAssembly(new Function() { + @Override + public ConnectableObservable apply(ConnectableObservable co) throws Exception { + if (co instanceof ObservablePublishAlt) { + return ObservablePublish.create(((ObservablePublishAlt)co).source()); + } + return co; + } + }); + } + + @After + public void after() { + RxJavaPlugins.setOnConnectableObservableAssembly(null); + } + @Test public void testPublish() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeLongTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeLongTest.java index d09ec0d6ce..dfe1713902 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeLongTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeLongTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.ArrayList; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeTest.java index cbdc0130ec..d54847c0b2 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRangeTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.ArrayList; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableReduceTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableReduceTest.java index cb6e61e8f6..c1c7ffbeb6 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableReduceTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableReduceTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountAltTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountAltTest.java new file mode 100644 index 0000000000..0f675bd15d --- /dev/null +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountAltTest.java @@ -0,0 +1,1421 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.internal.operators.observable; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.Test; +import org.mockito.InOrder; + +import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.disposables.*; +import io.reactivex.exceptions.*; +import io.reactivex.functions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.operators.observable.ObservableRefCount.RefConnection; +import io.reactivex.internal.util.ExceptionHelper; +import io.reactivex.observables.ConnectableObservable; +import io.reactivex.observers.TestObserver; +import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.*; +import io.reactivex.subjects.*; + +public class ObservableRefCountAltTest { + + @Test + public void testRefCountAsync() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger nextCount = new AtomicInteger(); + Observable r = Observable.interval(0, 25, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Disposable d) { + subscribeCount.incrementAndGet(); + } + }) + .doOnNext(new Consumer() { + @Override + public void accept(Long l) { + nextCount.incrementAndGet(); + } + }) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + Disposable d1 = r.subscribe(new Consumer() { + @Override + public void accept(Long l) { + receivedCount.incrementAndGet(); + } + }); + + Disposable d2 = r.subscribe(); + + // give time to emit + try { + Thread.sleep(260); + } catch (InterruptedException e) { + } + + // now unsubscribe + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one Observer getting a value but not the other + d1.dispose(); + + System.out.println("onNext: " + nextCount.get()); + + // should emit once for both subscribers + assertEquals(nextCount.get(), receivedCount.get()); + // only 1 subscribe + assertEquals(1, subscribeCount.get()); + } + + @Test + public void testRefCountSynchronous() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger nextCount = new AtomicInteger(); + Observable r = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Disposable d) { + subscribeCount.incrementAndGet(); + } + }) + .doOnNext(new Consumer() { + @Override + public void accept(Integer l) { + nextCount.incrementAndGet(); + } + }) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + Disposable d1 = r.subscribe(new Consumer() { + @Override + public void accept(Integer l) { + receivedCount.incrementAndGet(); + } + }); + + Disposable d2 = r.subscribe(); + + // give time to emit + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + + // now unsubscribe + d2.dispose(); // unsubscribe s2 first as we're counting in 1 and there can be a race between unsubscribe and one Observer getting a value but not the other + d1.dispose(); + + System.out.println("onNext Count: " + nextCount.get()); + + // it will emit twice because it is synchronous + assertEquals(nextCount.get(), receivedCount.get() * 2); + // it will subscribe twice because it is synchronous + assertEquals(2, subscribeCount.get()); + } + + @Test + public void testRefCountSynchronousTake() { + final AtomicInteger nextCount = new AtomicInteger(); + Observable r = Observable.just(1, 2, 3, 4, 5, 6, 7, 8, 9) + .doOnNext(new Consumer() { + @Override + public void accept(Integer l) { + System.out.println("onNext --------> " + l); + nextCount.incrementAndGet(); + } + }) + .take(4) + .publish().refCount(); + + final AtomicInteger receivedCount = new AtomicInteger(); + r.subscribe(new Consumer() { + @Override + public void accept(Integer l) { + receivedCount.incrementAndGet(); + } + }); + + System.out.println("onNext: " + nextCount.get()); + + assertEquals(4, receivedCount.get()); + assertEquals(4, receivedCount.get()); + } + + @Test + public void testRepeat() { + final AtomicInteger subscribeCount = new AtomicInteger(); + final AtomicInteger unsubscribeCount = new AtomicInteger(); + Observable r = Observable.interval(0, 1, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Disposable d) { + System.out.println("******************************* Subscribe received"); + // when we are subscribed + subscribeCount.incrementAndGet(); + } + }) + .doOnDispose(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + unsubscribeCount.incrementAndGet(); + } + }) + .publish().refCount(); + + for (int i = 0; i < 10; i++) { + TestObserver to1 = new TestObserver(); + TestObserver to2 = new TestObserver(); + r.subscribe(to1); + r.subscribe(to2); + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + to1.dispose(); + to2.dispose(); + to1.assertNoErrors(); + to2.assertNoErrors(); + assertTrue(to1.valueCount()> 0); + assertTrue(to2.valueCount()> 0); + } + + assertEquals(10, subscribeCount.get()); + assertEquals(10, unsubscribeCount.get()); + } + + @Test + public void testConnectUnsubscribe() throws InterruptedException { + final CountDownLatch unsubscribeLatch = new CountDownLatch(1); + final CountDownLatch subscribeLatch = new CountDownLatch(1); + + Observable o = synchronousInterval() + .doOnSubscribe(new Consumer() { + @Override + public void accept(Disposable d) { + System.out.println("******************************* Subscribe received"); + // when we are subscribed + subscribeLatch.countDown(); + } + }) + .doOnDispose(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + unsubscribeLatch.countDown(); + } + }); + + TestObserver observer = new TestObserver(); + o.publish().refCount().subscribeOn(Schedulers.newThread()).subscribe(observer); + System.out.println("send unsubscribe"); + // wait until connected + subscribeLatch.await(); + // now unsubscribe + observer.dispose(); + System.out.println("DONE sending unsubscribe ... now waiting"); + if (!unsubscribeLatch.await(3000, TimeUnit.MILLISECONDS)) { + System.out.println("Errors: " + observer.errors()); + if (observer.errors().size()> 0) { + observer.errors().get(0).printStackTrace(); + } + fail("timed out waiting for unsubscribe"); + } + observer.assertNoErrors(); + } + + @Test + public void testConnectUnsubscribeRaceConditionLoop() throws InterruptedException { + for (int i = 0; i < 100; i++) { + testConnectUnsubscribeRaceCondition(); + } + } + + @Test + public void testConnectUnsubscribeRaceCondition() throws InterruptedException { + final AtomicInteger subUnsubCount = new AtomicInteger(); + Observable o = synchronousInterval() + .doOnDispose(new Action() { + @Override + public void run() { + System.out.println("******************************* Unsubscribe received"); + // when we are unsubscribed + subUnsubCount.decrementAndGet(); + } + }) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Disposable d) { + System.out.println("******************************* SUBSCRIBE received"); + subUnsubCount.incrementAndGet(); + } + }); + + TestObserver observer = new TestObserver(); + + o.publish().refCount().subscribeOn(Schedulers.computation()).subscribe(observer); + System.out.println("send unsubscribe"); + // now immediately unsubscribe while subscribeOn is racing to subscribe + observer.dispose(); + + // this generally will mean it won't even subscribe as it is already unsubscribed by the time connect() gets scheduled + // give time to the counter to update + Thread.sleep(10); + + // make sure we wait a bit in case the counter is still nonzero + int counter = 200; + while (subUnsubCount.get() != 0 && counter-- != 0) { + Thread.sleep(10); + } + // either we subscribed and then unsubscribed, or we didn't ever even subscribe + assertEquals(0, subUnsubCount.get()); + + System.out.println("DONE sending unsubscribe ... now waiting"); + System.out.println("Errors: " + observer.errors()); + if (observer.errors().size()> 0) { + observer.errors().get(0).printStackTrace(); + } + observer.assertNoErrors(); + } + + private Observable synchronousInterval() { + return Observable.unsafeCreate(new ObservableSource() { + @Override + public void subscribe(Observer observer) { + final AtomicBoolean cancel = new AtomicBoolean(); + observer.onSubscribe(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + cancel.set(true); + } + })); + for (;;) { + if (cancel.get()) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + observer.onNext(1L); + } + } + }); + } + + @Test + public void onlyFirstShouldSubscribeAndLastUnsubscribe() { + final AtomicInteger subscriptionCount = new AtomicInteger(); + final AtomicInteger unsubscriptionCount = new AtomicInteger(); + Observable o = Observable.unsafeCreate(new ObservableSource() { + @Override + public void subscribe(Observer observer) { + subscriptionCount.incrementAndGet(); + observer.onSubscribe(Disposables.fromRunnable(new Runnable() { + @Override + public void run() { + unsubscriptionCount.incrementAndGet(); + } + })); + } + }); + Observable refCounted = o.publish().refCount(); + + Disposable first = refCounted.subscribe(); + assertEquals(1, subscriptionCount.get()); + + Disposable second = refCounted.subscribe(); + assertEquals(1, subscriptionCount.get()); + + first.dispose(); + assertEquals(0, unsubscriptionCount.get()); + + second.dispose(); + assertEquals(1, unsubscriptionCount.get()); + } + + @Test + public void testRefCount() { + TestScheduler s = new TestScheduler(); + Observable interval = Observable.interval(100, TimeUnit.MILLISECONDS, s).publish().refCount(); + + // subscribe list1 + final List list1 = new ArrayList(); + Disposable d1 = interval.subscribe(new Consumer() { + @Override + public void accept(Long t1) { + list1.add(t1); + } + }); + + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list1.size()); + assertEquals(0L, list1.get(0).longValue()); + assertEquals(1L, list1.get(1).longValue()); + + // subscribe list2 + final List list2 = new ArrayList(); + Disposable d2 = interval.subscribe(new Consumer() { + @Override + public void accept(Long t1) { + list2.add(t1); + } + }); + + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should have 5 items + assertEquals(5, list1.size()); + assertEquals(2L, list1.get(2).longValue()); + assertEquals(3L, list1.get(3).longValue()); + assertEquals(4L, list1.get(4).longValue()); + + // list 2 should only have 3 items + assertEquals(3, list2.size()); + assertEquals(2L, list2.get(0).longValue()); + assertEquals(3L, list2.get(1).longValue()); + assertEquals(4L, list2.get(2).longValue()); + + // unsubscribe list1 + d1.dispose(); + + // advance further + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should still have 5 items + assertEquals(5, list1.size()); + + // list 2 should have 6 items + assertEquals(6, list2.size()); + assertEquals(5L, list2.get(3).longValue()); + assertEquals(6L, list2.get(4).longValue()); + assertEquals(7L, list2.get(5).longValue()); + + // unsubscribe list2 + d2.dispose(); + + // advance further + s.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + // subscribing a new one should start over because the source should have been unsubscribed + // subscribe list3 + final List list3 = new ArrayList(); + interval.subscribe(new Consumer() { + @Override + public void accept(Long t1) { + list3.add(t1); + } + }); + + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list3.size()); + assertEquals(0L, list3.get(0).longValue()); + assertEquals(1L, list3.get(1).longValue()); + + } + + @Test + public void testAlreadyUnsubscribedClient() { + Observer done = DisposingObserver.INSTANCE; + + Observer o = TestHelper.mockObserver(); + + Observable result = Observable.just(1).publish().refCount(); + + result.subscribe(done); + + result.subscribe(o); + + verify(o).onNext(1); + verify(o).onComplete(); + verify(o, never()).onError(any(Throwable.class)); + } + + @Test + public void testAlreadyUnsubscribedInterleavesWithClient() { + ReplaySubject source = ReplaySubject.create(); + + Observer done = DisposingObserver.INSTANCE; + + Observer o = TestHelper.mockObserver(); + InOrder inOrder = inOrder(o); + + Observable result = source.publish().refCount(); + + result.subscribe(o); + + source.onNext(1); + + result.subscribe(done); + + source.onNext(2); + source.onComplete(); + + inOrder.verify(o).onNext(1); + inOrder.verify(o).onNext(2); + inOrder.verify(o).onComplete(); + verify(o, never()).onError(any(Throwable.class)); + } + + @Test + public void testConnectDisconnectConnectAndSubjectState() { + Observable o1 = Observable.just(10); + Observable o2 = Observable.just(20); + Observable combined = Observable.combineLatest(o1, o2, new BiFunction() { + @Override + public Integer apply(Integer t1, Integer t2) { + return t1 + t2; + } + }) + .publish().refCount(); + + TestObserver to1 = new TestObserver(); + TestObserver to2 = new TestObserver(); + + combined.subscribe(to1); + combined.subscribe(to2); + + to1.assertTerminated(); + to1.assertNoErrors(); + to1.assertValue(30); + + to2.assertTerminated(); + to2.assertNoErrors(); + to2.assertValue(30); + } + + @Test(timeout = 10000) + public void testUpstreamErrorAllowsRetry() throws InterruptedException { + final AtomicInteger intervalSubscribed = new AtomicInteger(); + Observable interval = + Observable.interval(200, TimeUnit.MILLISECONDS) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Disposable d) { + System.out.println("Subscribing to interval " + intervalSubscribed.incrementAndGet()); + } + } + ) + .flatMap(new Function>() { + @Override + public Observable apply(Long t1) { + return Observable.defer(new Callable>() { + @Override + public Observable call() { + return Observable.error(new Exception("Some exception")); + } + }); + } + }) + .onErrorResumeNext(new Function>() { + @Override + public Observable apply(Throwable t1) { + return Observable.error(t1); + } + }) + .publish() + .refCount(); + + interval + .doOnError(new Consumer() { + @Override + public void accept(Throwable t1) { + System.out.println("Observer 1 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer() { + @Override + public void accept(String t1) { + System.out.println("Observer 1: " + t1); + } + }); + Thread.sleep(100); + interval + .doOnError(new Consumer() { + @Override + public void accept(Throwable t1) { + System.out.println("Observer 2 onError: " + t1); + } + }) + .retry(5) + .subscribe(new Consumer() { + @Override + public void accept(String t1) { + System.out.println("Observer 2: " + t1); + } + }); + + Thread.sleep(1300); + + System.out.println(intervalSubscribed.get()); + assertEquals(6, intervalSubscribed.get()); + } + + private enum DisposingObserver implements Observer { + INSTANCE; + + @Override + public void onSubscribe(Disposable d) { + d.dispose(); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + } + + @Test + public void disposed() { + TestHelper.checkDisposed(Observable.just(1).publish().refCount()); + } + + @Test + public void noOpConnect() { + final int[] calls = { 0 }; + Observable o = new ConnectableObservable() { + @Override + public void connect(Consumer connection) { + calls[0]++; + } + + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposables.disposed()); + } + }.refCount(); + + o.test(); + o.test(); + + assertEquals(1, calls[0]); + } + Observable source; + + @Test + public void replayNoLeak() throws Exception { + System.gc(); + Thread.sleep(250); + + 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(250); + + 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(250); + + 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(); + + Disposable d1 = source.subscribe(); + Disposable d2 = source.subscribe(); + + d1.dispose(); + d2.dispose(); + + d1 = null; + d2 = null; + + System.gc(); + Thread.sleep(250); + + 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; + + ExceptionData(Object data) { + this.data = data; + } + } + + @Test + public void publishNoLeak() throws Exception { + System.gc(); + Thread.sleep(250); + + 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(); + + source.subscribe(Functions.emptyConsumer(), Functions.emptyConsumer()); + + long after = 0L; + + for (int i = 0; i < 10; i++) { + System.gc(); + + after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); + + if (start + 20 * 1000 * 1000> after) { + break; + } + + Thread.sleep(100); + } + + 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(250); + + 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(); + + Disposable d1 = source.test(); + Disposable d2 = source.test(); + + d1.dispose(); + d2.dispose(); + + d1 = null; + d2 = null; + + System.gc(); + Thread.sleep(250); + + 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).concatWith(Observable.never()) + .replay(); + + if (co instanceof Disposable) { + assertTrue(((Disposable)co).isDisposed()); + + Disposable connection = co.connect(); + + assertFalse(((Disposable)co).isDisposed()); + + connection.dispose(); + + assertTrue(((Disposable)co).isDisposed()); + } + } + + static final class BadObservableSubscribe extends ConnectableObservable { + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer observer) { + throw new TestException("subscribeActual"); + } + } + + static final class BadObservableDispose extends ConnectableObservable implements Disposable { + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposables.empty()); + } + } + + static final class BadObservableConnect extends ConnectableObservable { + + @Override + public void connect(Consumer connection) { + throw new TestException("connect"); + } + + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposables.empty()); + } + } + + @Test + public void badSourceSubscribe() { + BadObservableSubscribe bo = new BadObservableSubscribe(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + @Test + public void badSourceDispose() { + BadObservableDispose bo = new BadObservableDispose(); + + try { + bo.refCount() + .test() + .cancel(); + fail("Should have thrown"); + } catch (TestException expected) { + } + } + + @Test + public void badSourceConnect() { + BadObservableConnect bo = new BadObservableConnect(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + static final class BadObservableSubscribe2 extends ConnectableObservable { + + int count; + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer observer) { + if (++count == 1) { + observer.onSubscribe(Disposables.empty()); + } else { + throw new TestException("subscribeActual"); + } + } + } + + @Test + public void badSourceSubscribe2() { + BadObservableSubscribe2 bo = new BadObservableSubscribe2(); + + Observable o = bo.refCount(); + o.test(); + try { + o.test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + static final class BadObservableConnect2 extends ConnectableObservable + implements Disposable { + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + } + + @Override + public void dispose() { + throw new TestException("dispose"); + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void badSourceCompleteDisconnect() { + BadObservableConnect2 bo = new BadObservableConnect2(); + + try { + bo.refCount() + .test(); + fail("Should have thrown"); + } catch (NullPointerException ex) { + assertTrue(ex.getCause() instanceof TestException); + } + } + + @Test(timeout = 7500) + public void blockingSourceAsnycCancel() throws Exception { + BehaviorSubject bs = BehaviorSubject.createDefault(1); + + Observable o = bs + .replay(1) + .refCount(); + + o.subscribe(); + + final AtomicBoolean interrupted = new AtomicBoolean(); + + o.switchMap(new Function>() { + @Override + public ObservableSource apply(Integer v) throws Exception { + return Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter emitter) throws Exception { + while (!emitter.isDisposed()) { + Thread.sleep(100); + } + interrupted.set(true); + } + }); + } + }) + .take(500, TimeUnit.MILLISECONDS) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(); + + assertTrue(interrupted.get()); + } + + @Test + public void byCount() { + final int[] subscriptions = { 0 }; + + Observable source = Observable.range(1, 5) + .doOnSubscribe(new Consumer() { + @Override + public void accept(Disposable d) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(2); + + for (int i = 0; i < 3; i++) { + TestObserver to1 = source.test(); + + to1.withTag("to1 " + i); + to1.assertEmpty(); + + TestObserver to2 = source.test(); + + to2.withTag("to2 " + i); + + to1.assertResult(1, 2, 3, 4, 5); + to2.assertResult(1, 2, 3, 4, 5); + } + + assertEquals(3, subscriptions[0]); + } + + @Test + public void resubscribeBeforeTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishSubject ps = PublishSubject.create(); + + Observable source = ps + .doOnSubscribe(new Consumer() { + @Override + public void accept(Disposable d) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(500, TimeUnit.MILLISECONDS); + + TestObserver to1 = source.test(); + + assertEquals(1, subscriptions[0]); + + to1.cancel(); + + Thread.sleep(100); + + to1 = source.test(); + + assertEquals(1, subscriptions[0]); + + Thread.sleep(500); + + assertEquals(1, subscriptions[0]); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + ps.onNext(4); + ps.onNext(5); + ps.onComplete(); + + to1 + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void letitTimeout() throws Exception { + final int[] subscriptions = { 0 }; + + PublishSubject ps = PublishSubject.create(); + + Observable source = ps + .doOnSubscribe(new Consumer() { + @Override + public void accept(Disposable d) throws Exception { + subscriptions[0]++; + } + }) + .publish() + .refCount(1, 100, TimeUnit.MILLISECONDS); + + TestObserver to1 = source.test(); + + assertEquals(1, subscriptions[0]); + + to1.cancel(); + + assertTrue(ps.hasObservers()); + + Thread.sleep(200); + + assertFalse(ps.hasObservers()); + } + + @Test + public void error() { + Observable.error(new IOException()) + .publish() + .refCount(500, TimeUnit.MILLISECONDS) + .test() + .assertFailure(IOException.class); + } + + @Test + public void comeAndGo() { + PublishSubject ps = PublishSubject.create(); + + Observable source = ps + .publish() + .refCount(1); + + TestObserver to1 = source.test(); + + assertTrue(ps.hasObservers()); + + for (int i = 0; i < 3; i++) { + TestObserver to2 = source.test(); + to1.cancel(); + to1 = to2; + } + + to1.cancel(); + + assertFalse(ps.hasObservers()); + } + + @Test + public void unsubscribeSubscribeRace() { + for (int i = 0; i < 1000; i++) { + + final Observable source = Observable.range(1, 5) + .replay() + .refCount(1) + ; + + final TestObserver to1 = source.test(); + + final TestObserver to2 = new TestObserver(); + + Runnable r1 = new Runnable() { + @Override + public void run() { + to1.cancel(); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + source.subscribe(to2); + } + }; + + TestHelper.race(r1, r2, Schedulers.single()); + + to2 + .withTag("Round: " + i) + .assertResult(1, 2, 3, 4, 5); + } + } + + static final class BadObservableDoubleOnX extends ConnectableObservable + implements Disposable { + + @Override + public void connect(Consumer connection) { + try { + connection.accept(Disposables.empty()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposables.empty()); + observer.onSubscribe(Disposables.empty()); + observer.onComplete(); + observer.onComplete(); + observer.onError(new TestException()); + } + + @Override + public void dispose() { + } + + @Override + public boolean isDisposed() { + return false; + } + } + + @Test + public void doubleOnX() { + List errors = TestHelper.trackPluginErrors(); + try { + new BadObservableDoubleOnX() + .refCount() + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXCount() { + List errors = TestHelper.trackPluginErrors(); + try { + new BadObservableDoubleOnX() + .refCount(1) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void doubleOnXTime() { + List errors = TestHelper.trackPluginErrors(); + try { + new BadObservableDoubleOnX() + .refCount(5, TimeUnit.SECONDS, Schedulers.single()) + .test() + .assertResult(); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + TestHelper.assertUndeliverable(errors, 1, TestException.class); + } finally { + RxJavaPlugins.reset(); + } + } + + @Test + public void cancelTerminateStateExclusion() { + ObservableRefCount o = (ObservableRefCount)PublishSubject.create() + .publish() + .refCount(); + + o.cancel(null); + + o.cancel(new RefConnection(o)); + + RefConnection rc = new RefConnection(o); + o.connection = null; + rc.subscriberCount = 0; + o.timeout(rc); + + rc.subscriberCount = 1; + o.timeout(rc); + + o.connection = rc; + o.timeout(rc); + + rc.subscriberCount = 0; + o.timeout(rc); + + // ------------------- + + rc.subscriberCount = 2; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = false; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 2; + rc.connected = true; + o.connection = rc; + o.cancel(rc); + + rc.subscriberCount = 1; + rc.connected = true; + o.connection = rc; + rc.lazySet(null); + o.cancel(rc); + + o.connection = rc; + o.cancel(new RefConnection(o)); + } + + @Test + public void replayRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Observable observable = Observable.just(1).replay(1).refCount(); + + TestObserver observer1 = observable + .subscribeOn(Schedulers.io()) + .test(); + + TestObserver observer2 = observable + .subscribeOn(Schedulers.io()) + .test(); + + observer1 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + + observer2 + .withTag("" + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertResult(1); + } + } + + static final class TestConnectableObservable extends ConnectableObservable + implements Disposable { + + volatile boolean disposed; + + @Override + public void dispose() { + disposed = true; + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void connect(Consumer connection) { + // not relevant + } + + @Override + protected void subscribeActual(Observer observer) { + // not relevant + } + } + + @Test + public void timeoutDisposesSource() { + ObservableRefCount o = (ObservableRefCount)new TestConnectableObservable().refCount(); + + RefConnection rc = new RefConnection(o); + o.connection = rc; + + o.timeout(rc); + + assertTrue(((Disposable)o.source).isDisposed()); + } + + @Test + public void disconnectBeforeConnect() { + BehaviorSubject subject = BehaviorSubject.create(); + + Observable observable = subject + .replay(1) + .refCount(); + + observable.takeUntil(Observable.just(1)).test(); + + subject.onNext(2); + + observable.take(1).test().assertResult(2); + } + + @Test + public void publishRefCountShallBeThreadSafe() { + for (int i = 0; i < TestHelper.RACE_LONG_LOOPS; i++) { + Observable observable = Observable.just(1).publish().refCount(); + + TestObserver observer1 = observable + .subscribeOn(Schedulers.io()) + .test(); + + TestObserver observer2 = observable + .subscribeOn(Schedulers.io()) + .test(); + + observer1 + .withTag("observer1 " + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + + observer2 + .withTag("observer2 " + i) + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + } + } + + @Test + public void upstreamTerminationTriggersAnotherCancel() throws Exception { + ReplaySubject rs = ReplaySubject.create(); + rs.onNext(1); + rs.onComplete(); + + Observable shared = rs.share(); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + } +} diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountTest.java index 0f0d930d8d..485afc54e1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRefCountTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; @@ -23,7 +22,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.*; -import org.junit.Test; +import org.junit.*; import org.mockito.InOrder; import io.reactivex.*; @@ -43,6 +42,27 @@ public class ObservableRefCountTest { + // This will undo the workaround so that the plain ObservablePublish is still + // tested. + @Before + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void before() { + RxJavaPlugins.setOnConnectableObservableAssembly(new Function() { + @Override + public ConnectableObservable apply(ConnectableObservable co) throws Exception { + if (co instanceof ObservablePublishAlt) { + return ObservablePublish.create(((ObservablePublishAlt)co).source()); + } + return co; + } + }); + } + + @After + public void after() { + RxJavaPlugins.setOnConnectableObservableAssembly(null); + } + @Test public void testRefCountAsync() { final AtomicInteger subscribeCount = new AtomicInteger(); @@ -631,7 +651,7 @@ protected void subscribeActual(Observer observer) { @Test public void replayNoLeak() throws Exception { System.gc(); - Thread.sleep(100); + Thread.sleep(250); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -647,7 +667,7 @@ public Object call() throws Exception { source.subscribe(); System.gc(); - Thread.sleep(100); + Thread.sleep(250); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -658,7 +678,7 @@ public Object call() throws Exception { @Test public void replayNoLeak2() throws Exception { System.gc(); - Thread.sleep(100); + Thread.sleep(250); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -681,7 +701,7 @@ public Object call() throws Exception { d2 = null; System.gc(); - Thread.sleep(100); + Thread.sleep(250); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -702,7 +722,7 @@ static final class ExceptionData extends Exception { @Test public void publishNoLeak() throws Exception { System.gc(); - Thread.sleep(100); + Thread.sleep(250); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -718,7 +738,7 @@ public Object call() throws Exception { source.subscribe(Functions.emptyConsumer(), Functions.emptyConsumer()); System.gc(); - Thread.sleep(100); + Thread.sleep(250); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -729,7 +749,7 @@ public Object call() throws Exception { @Test public void publishNoLeak2() throws Exception { System.gc(); - Thread.sleep(100); + Thread.sleep(250); long start = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -752,7 +772,7 @@ public Object call() throws Exception { d2 = null; System.gc(); - Thread.sleep(100); + Thread.sleep(250); long after = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed(); @@ -1360,4 +1380,23 @@ public void disconnectBeforeConnect() { observable.take(1).test().assertResult(2); } + + @Test + public void upstreamTerminationTriggersAnotherCancel() throws Exception { + ReplaySubject rs = ReplaySubject.create(); + rs.onNext(1); + rs.onComplete(); + + Observable shared = rs.share(); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + + shared + .buffer(shared.debounce(5, TimeUnit.SECONDS)) + .test() + .assertValueCount(2); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java index dbb72e21ef..538b4a0c69 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRepeatTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableReplayTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableReplayTest.java index 40d09f4f33..7c768f36cc 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableReplayTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableReplayTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.management.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java index 0055449ef5..ab7998c8a1 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryWithPredicateTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryWithPredicateTest.java index 7b0322ab87..debeb8eec4 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryWithPredicateTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableRetryWithPredicateTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java index 01f04653f2..7e3098216c 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSampleTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableScanTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableScanTest.java index 35df1a462e..e078f92505 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableScanTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableScanTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualTest.java index 4a7a33ddd6..da996cfc22 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSequenceEqualTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.*; import org.junit.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java index b89d70b8df..c97f851700 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.Arrays; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimedTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimedTest.java index 50df641a26..3cf4d2e155 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipLastTimedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java index d4772b1a6a..9e809b88cc 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSkipWhileTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import org.junit.Test; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchTest.java index 610f49eac4..83b5a51270 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableSwitchTest.java @@ -17,7 +17,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -import java.util.List; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.*; @@ -25,16 +25,18 @@ import org.mockito.InOrder; import io.reactivex.*; +import io.reactivex.Observable; +import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.schedulers.ImmediateThinScheduler; import io.reactivex.internal.util.ExceptionHelper; -import io.reactivex.observers.TestObserver; +import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.*; -import io.reactivex.subjects.*; +import io.reactivex.subjects.PublishSubject; public class ObservableSwitchTest { @@ -1192,4 +1194,102 @@ public Object apply(Integer w) throws Exception { .assertNoErrors() .assertComplete(); } + + @Test + public void switchMapFusedIterable() { + Observable.range(1, 2) + .switchMap(new Function>() { + @Override + public Observable apply(Integer v) + throws Exception { + return Observable.fromIterable(Arrays.asList(v * 10)); + } + }) + .test() + .assertResult(10, 20); + } + + @Test + public void switchMapHiddenIterable() { + Observable.range(1, 2) + .switchMap(new Function>() { + @Override + public Observable apply(Integer v) + throws Exception { + return Observable.fromIterable(Arrays.asList(v * 10)).hide(); + } + }) + .test() + .assertResult(10, 20); + } + + @Test + public void cancellationShouldTriggerInnerCancellationRace() throws Throwable { + final AtomicInteger outer = new AtomicInteger(); + final AtomicInteger inner = new AtomicInteger(); + + int n = 10000; + for (int i = 0; i < n; i++) { + Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter it) + throws Exception { + it.onNext(0); + } + }) + .switchMap(new Function>() { + @Override + public ObservableSource apply(Integer v) + throws Exception { + return createObservable(inner); + } + }) + .observeOn(Schedulers.computation()) + .doFinally(new Action() { + @Override + public void run() throws Exception { + outer.incrementAndGet(); + } + }) + .take(1) + .blockingSubscribe(Functions.emptyConsumer(), new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + e.printStackTrace(); + } + }); + } + + Thread.sleep(100); + assertEquals(inner.get(), outer.get()); + assertEquals(n, inner.get()); + } + + Observable createObservable(final AtomicInteger inner) { + return Observable.unsafeCreate(new ObservableSource() { + @Override + public void subscribe(Observer observer) { + final SerializedObserver it = new SerializedObserver(observer); + it.onSubscribe(Disposables.empty()); + Schedulers.io().scheduleDirect(new Runnable() { + @Override + public void run() { + it.onNext(1); + } + }, 0, TimeUnit.MILLISECONDS); + Schedulers.io().scheduleDirect(new Runnable() { + @Override + public void run() { + it.onNext(2); + } + }, 0, TimeUnit.MILLISECONDS); + } + }) + .doFinally(new Action() { + @Override + public void run() throws Exception { + inner.incrementAndGet(); + } + }); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTest.java index 34ab87312b..1eb89adc6a 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.atomic.AtomicInteger; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimedTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimedTest.java index bc933f9b02..2aa3f38c7e 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimedTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeLastTimedTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; @@ -276,4 +275,27 @@ public void run() { TestHelper.race(r1, r2); } } + + @Test + public void lastWindowIsFixedInTime() { + TimesteppingScheduler scheduler = new TimesteppingScheduler(); + scheduler.stepEnabled = false; + + PublishSubject ps = PublishSubject.create(); + + TestObserver to = ps + .takeLast(2, TimeUnit.SECONDS, scheduler) + .test(); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + ps.onNext(4); + + scheduler.stepEnabled = true; + + ps.onComplete(); + + to.assertResult(1, 2, 3, 4); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeTest.java index a9b9ae35d9..341409f958 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicateTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicateTest.java index 0e6f040972..9fa6c21bca 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicateTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTakeUntilPredicateTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java index 935922b67c..9d517ec1be 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutTests.java @@ -15,7 +15,6 @@ import static io.reactivex.internal.util.ExceptionHelper.timeoutMessage; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.IOException; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutWithSelectorTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutWithSelectorTest.java index 3dad40f34a..a0d8a93ebe 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutWithSelectorTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimeoutWithSelectorTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java index c8af9d88b8..124283dc1f 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableTimerTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java index 18447143aa..1f28cfc5d2 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableToListTest.java @@ -13,7 +13,6 @@ package io.reactivex.internal.operators.observable; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java index 2b7d7f4141..1067ff37c9 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableUnsubscribeOnTest.java @@ -72,7 +72,7 @@ public void subscribe(Observer t1) { System.out.println("unsubscribeThread: " + unsubscribeThread); System.out.println("subscribeThread.get(): " + subscribeThread.get()); - assertTrue(unsubscribeThread.toString(), unsubscribeThread == uiEventLoop.getThread()); + assertSame(unsubscribeThread.toString(), unsubscribeThread, uiEventLoop.getThread()); observer.assertValues(1, 2); observer.assertTerminated(); diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithObservableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithObservableTest.java index a82e876464..9f8969222b 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithObservableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithObservableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithTimeTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithTimeTest.java index 4eb90f4e50..8500d52948 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithTimeTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableWindowWithTimeTest.java @@ -16,8 +16,8 @@ import static org.junit.Assert.*; import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; import org.junit.*; @@ -64,17 +64,19 @@ public void subscribe(Observer observer) { Observable> windowed = source.window(100, TimeUnit.MILLISECONDS, scheduler, 2); windowed.subscribe(observeWindow(list, lists)); - scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); + scheduler.advanceTimeTo(95, TimeUnit.MILLISECONDS); assertEquals(1, lists.size()); assertEquals(lists.get(0), list("one", "two")); - scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); - assertEquals(2, lists.size()); - assertEquals(lists.get(1), list("three", "four")); + scheduler.advanceTimeTo(195, TimeUnit.MILLISECONDS); + assertEquals(3, lists.size()); + assertTrue(lists.get(1).isEmpty()); + assertEquals(lists.get(2), list("three", "four")); scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); - assertEquals(3, lists.size()); - assertEquals(lists.get(2), list("five")); + assertEquals(5, lists.size()); + assertTrue(lists.get(3).isEmpty()); + assertEquals(lists.get(4), list("five")); } @Test @@ -706,4 +708,244 @@ public void countRestartsOnTimeTick() { .assertNoErrors() .assertNotComplete(); } + + @Test + public void exactTimeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Observable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Observable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeAndSizeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(100, TimeUnit.MILLISECONDS, 10) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Observable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void exactTimeAndSizeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(100, TimeUnit.MILLISECONDS, 10) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Observable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void skipTimeAndSizeBoundNoInterruptWindowOutputOnComplete() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(90, 100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Observable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onComplete(); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } + + @Test + public void skipTimeAndSizeBoundNoInterruptWindowOutputOnError() throws Exception { + final AtomicBoolean isInterrupted = new AtomicBoolean(); + + final PublishSubject ps = PublishSubject.create(); + + final CountDownLatch doOnNextDone = new CountDownLatch(1); + final CountDownLatch secondWindowProcessing = new CountDownLatch(1); + + ps.window(90, 100, TimeUnit.MILLISECONDS) + .doOnNext(new Consumer>() { + int count; + @Override + public void accept(Observable v) throws Exception { + System.out.println(Thread.currentThread()); + if (count++ == 1) { + secondWindowProcessing.countDown(); + try { + Thread.sleep(200); + isInterrupted.set(Thread.interrupted()); + } catch (InterruptedException ex) { + isInterrupted.set(true); + } + doOnNextDone.countDown(); + } + } + }) + .test(); + + ps.onNext(1); + + assertTrue(secondWindowProcessing.await(5, TimeUnit.SECONDS)); + + ps.onError(new TestException()); + + assertTrue(doOnNextDone.await(5, TimeUnit.SECONDS)); + + assertFalse("The doOnNext got interrupted!", isInterrupted.get()); + } } diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableZipIterableTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableZipIterableTest.java index 971359d8ad..f05c41cfd3 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableZipIterableTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableZipIterableTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/internal/operators/observable/ObservableZipTest.java b/src/test/java/io/reactivex/internal/operators/observable/ObservableZipTest.java index 2fc7d7cb52..eca18c71b3 100644 --- a/src/test/java/io/reactivex/internal/operators/observable/ObservableZipTest.java +++ b/src/test/java/io/reactivex/internal/operators/observable/ObservableZipTest.java @@ -14,7 +14,6 @@ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; @@ -1351,6 +1350,34 @@ public Object apply(Object[] a) throws Exception { .assertResult("[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]"); } + /** + * Ensures that an ObservableSource implementation can be supplied that doesn't subclass Observable + */ + @Test + public void zipIterableNotSubclassingObservable() { + final ObservableSource s1 = new ObservableSource() { + @Override + public void subscribe (final Observer observer) { + Observable.just(1).subscribe(observer); + } + }; + final ObservableSource s2 = new ObservableSource() { + @Override + public void subscribe (final Observer observer) { + Observable.just(2).subscribe(observer); + } + }; + + Observable.zip(Arrays.asList(s1, s2), new Function() { + @Override + public Object apply(Object[] a) throws Exception { + return Arrays.toString(a); + } + }) + .test() + .assertResult("[1, 2]"); + } + @Test public void dispose() { TestHelper.checkDisposed(Observable.zip(Observable.just(1), Observable.just(1), new BiFunction() { @@ -1428,4 +1455,35 @@ public Integer apply(Integer t1, Integer t2) throws Exception { ps2.onNext(2); to.assertResult(3); } + + @Test + public void firstErrorPreventsSecondSubscription() { + final AtomicInteger counter = new AtomicInteger(); + + List> observableList = new ArrayList>(); + observableList.add(Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter e) + throws Exception { throw new TestException(); } + })); + observableList.add(Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter e) + throws Exception { counter.getAndIncrement(); } + })); + + Observable.zip(observableList, + new Function() { + @Override + public Object apply(Object[] a) throws Exception { + return a; + } + }) + .test() + .assertFailure(TestException.class) + ; + + assertEquals(0, counter.get()); + } + } diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java index ba15f9f71b..425ee84205 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleDoOnTerminateTest.java @@ -19,7 +19,6 @@ import io.reactivex.exceptions.TestException; import io.reactivex.functions.Action; import io.reactivex.observers.TestObserver; -import org.junit.Assert; import org.junit.Test; import java.util.List; diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleFromCallableTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleFromCallableTest.java index 2c4ebcc321..23fb50c01d 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleFromCallableTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleFromCallableTest.java @@ -33,7 +33,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; public class SingleFromCallableTest { diff --git a/src/test/java/io/reactivex/internal/operators/single/SingleMiscTest.java b/src/test/java/io/reactivex/internal/operators/single/SingleMiscTest.java index c811291851..7b3a891680 100644 --- a/src/test/java/io/reactivex/internal/operators/single/SingleMiscTest.java +++ b/src/test/java/io/reactivex/internal/operators/single/SingleMiscTest.java @@ -27,7 +27,9 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; @@ -199,11 +201,13 @@ public boolean test(Integer i, Throwable e) throws Exception { @Test public void retryTimes() { + final AtomicInteger calls = new AtomicInteger(); + Single.fromCallable(new Callable() { - int c; + @Override public Object call() throws Exception { - if (++c != 5) { + if (calls.incrementAndGet() != 6) { throw new TestException(); } return 1; @@ -212,6 +216,8 @@ public Object call() throws Exception { .retry(5) .test() .assertResult(1); + + assertEquals(6, calls.get()); } @Test diff --git a/src/test/java/io/reactivex/internal/schedulers/IoScheduledReleaseTest.java b/src/test/java/io/reactivex/internal/schedulers/IoScheduledReleaseTest.java new file mode 100644 index 0000000000..7eaaa377c0 --- /dev/null +++ b/src/test/java/io/reactivex/internal/schedulers/IoScheduledReleaseTest.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.internal.schedulers; + +import io.reactivex.Completable; +import io.reactivex.Flowable; +import io.reactivex.annotations.NonNull; +import io.reactivex.functions.Function; +import io.reactivex.schedulers.Schedulers; +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +public class IoScheduledReleaseTest { + + /* This test will be stuck in a deadlock if IoScheduler.USE_SCHEDULED_RELEASE is not set */ + @Test + public void scheduledRelease() { + boolean savedScheduledRelease = IoScheduler.USE_SCHEDULED_RELEASE; + IoScheduler.USE_SCHEDULED_RELEASE = true; + try { + Flowable.just("item") + .observeOn(Schedulers.io()) + .firstOrError() + .map(new Function() { + @Override + public String apply(@NonNull final String item) throws Exception { + for (int i = 0; i < 50; i++) { + Completable.complete() + .observeOn(Schedulers.io()) + .blockingAwait(); + } + return "Done"; + } + }) + .ignoreElement() + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertComplete(); + } finally { + IoScheduler.USE_SCHEDULED_RELEASE = savedScheduledRelease; + } + } +} diff --git a/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java b/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java index 603929ddb4..24b3daa801 100644 --- a/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java +++ b/src/test/java/io/reactivex/internal/schedulers/SchedulerPoolFactoryTest.java @@ -18,12 +18,11 @@ import static org.junit.Assert.*; -import java.util.Properties; - import org.junit.Test; import io.reactivex.TestHelper; -import io.reactivex.internal.schedulers.SchedulerPoolFactory.PurgeProperties; +import io.reactivex.functions.Function; +import io.reactivex.internal.functions.Functions; import io.reactivex.schedulers.Schedulers; public class SchedulerPoolFactoryTest { @@ -78,53 +77,66 @@ public void run() { } @Test - public void loadPurgeProperties() { - Properties props1 = new Properties(); - - PurgeProperties pp = new PurgeProperties(); - pp.load(props1); - - assertTrue(pp.purgeEnable); - assertEquals(pp.purgePeriod, 1); + public void boolPropertiesDisabledReturnsDefaultDisabled() throws Throwable { + assertTrue(SchedulerPoolFactory.getBooleanProperty(false, "key", false, true, failingPropertiesAccessor)); + assertFalse(SchedulerPoolFactory.getBooleanProperty(false, "key", true, false, failingPropertiesAccessor)); } @Test - public void loadPurgePropertiesDisabled() { - Properties props1 = new Properties(); - props1.setProperty(SchedulerPoolFactory.PURGE_ENABLED_KEY, "false"); + public void boolPropertiesEnabledMissingReturnsDefaultMissing() throws Throwable { + assertTrue(SchedulerPoolFactory.getBooleanProperty(true, "key", true, false, missingPropertiesAccessor)); + assertFalse(SchedulerPoolFactory.getBooleanProperty(true, "key", false, true, missingPropertiesAccessor)); + } - PurgeProperties pp = new PurgeProperties(); - pp.load(props1); + @Test + public void boolPropertiesFailureReturnsDefaultMissing() throws Throwable { + assertTrue(SchedulerPoolFactory.getBooleanProperty(true, "key", true, false, failingPropertiesAccessor)); + assertFalse(SchedulerPoolFactory.getBooleanProperty(true, "key", false, true, failingPropertiesAccessor)); + } - assertFalse(pp.purgeEnable); - assertEquals(pp.purgePeriod, 1); + @Test + public void boolPropertiesReturnsValue() throws Throwable { + assertTrue(SchedulerPoolFactory.getBooleanProperty(true, "true", true, false, Functions.identity())); + assertFalse(SchedulerPoolFactory.getBooleanProperty(true, "false", false, true, Functions.identity())); } @Test - public void loadPurgePropertiesEnabledCustomPeriod() { - Properties props1 = new Properties(); - props1.setProperty(SchedulerPoolFactory.PURGE_ENABLED_KEY, "true"); - props1.setProperty(SchedulerPoolFactory.PURGE_PERIOD_SECONDS_KEY, "2"); + public void intPropertiesDisabledReturnsDefaultDisabled() throws Throwable { + assertEquals(-1, SchedulerPoolFactory.getIntProperty(false, "key", 0, -1, failingPropertiesAccessor)); + assertEquals(-1, SchedulerPoolFactory.getIntProperty(false, "key", 1, -1, failingPropertiesAccessor)); + } - PurgeProperties pp = new PurgeProperties(); - pp.load(props1); + @Test + public void intPropertiesEnabledMissingReturnsDefaultMissing() throws Throwable { + assertEquals(-1, SchedulerPoolFactory.getIntProperty(true, "key", -1, 0, missingPropertiesAccessor)); + assertEquals(-1, SchedulerPoolFactory.getIntProperty(true, "key", -1, 1, missingPropertiesAccessor)); + } - assertTrue(pp.purgeEnable); - assertEquals(pp.purgePeriod, 2); + @Test + public void intPropertiesFailureReturnsDefaultMissing() throws Throwable { + assertEquals(-1, SchedulerPoolFactory.getIntProperty(true, "key", -1, 0, failingPropertiesAccessor)); + assertEquals(-1, SchedulerPoolFactory.getIntProperty(true, "key", -1, 1, failingPropertiesAccessor)); } @Test - public void loadPurgePropertiesEnabledCustomPeriodNaN() { - Properties props1 = new Properties(); - props1.setProperty(SchedulerPoolFactory.PURGE_ENABLED_KEY, "true"); - props1.setProperty(SchedulerPoolFactory.PURGE_PERIOD_SECONDS_KEY, "abc"); + public void intPropertiesReturnsValue() throws Throwable { + assertEquals(1, SchedulerPoolFactory.getIntProperty(true, "1", 0, 4, Functions.identity())); + assertEquals(2, SchedulerPoolFactory.getIntProperty(true, "2", 3, 5, Functions.identity())); + } - PurgeProperties pp = new PurgeProperties(); - pp.load(props1); + static final Function failingPropertiesAccessor = new Function() { + @Override + public String apply(String v) throws Exception { + throw new SecurityException(); + } + }; - assertTrue(pp.purgeEnable); - assertEquals(pp.purgePeriod, 1); - } + static final Function missingPropertiesAccessor = new Function() { + @Override + public String apply(String v) throws Exception { + return null; + } + }; @Test public void putIntoPoolNoPurge() { diff --git a/src/test/java/io/reactivex/internal/util/NotificationLiteTest.java b/src/test/java/io/reactivex/internal/util/NotificationLiteTest.java index 36bfe52566..f6e463c7da 100644 --- a/src/test/java/io/reactivex/internal/util/NotificationLiteTest.java +++ b/src/test/java/io/reactivex/internal/util/NotificationLiteTest.java @@ -45,6 +45,6 @@ public void errorNotificationCompare() { assertEquals(ex.hashCode(), n1.hashCode()); - assertFalse(n1.equals(NotificationLite.complete())); + assertNotEquals(n1, NotificationLite.complete()); } } diff --git a/src/test/java/io/reactivex/internal/util/VolatileSizeArrayListTest.java b/src/test/java/io/reactivex/internal/util/VolatileSizeArrayListTest.java index 6086fff7e3..00d3119b32 100644 --- a/src/test/java/io/reactivex/internal/util/VolatileSizeArrayListTest.java +++ b/src/test/java/io/reactivex/internal/util/VolatileSizeArrayListTest.java @@ -82,22 +82,22 @@ public void normal() { VolatileSizeArrayList list2 = new VolatileSizeArrayList(); list2.addAll(Arrays.asList(1, 2, 3, 4, 5, 6)); - assertFalse(list2.equals(list)); - assertFalse(list.equals(list2)); + assertNotEquals(list2, list); + assertNotEquals(list, list2); list2.add(7); - assertTrue(list2.equals(list)); - assertTrue(list.equals(list2)); + assertEquals(list2, list); + assertEquals(list, list2); List list3 = new ArrayList(); list3.addAll(Arrays.asList(1, 2, 3, 4, 5, 6)); - assertFalse(list3.equals(list)); - assertFalse(list.equals(list3)); + assertNotEquals(list3, list); + assertNotEquals(list, list3); list3.add(7); - assertTrue(list3.equals(list)); - assertTrue(list.equals(list3)); + assertEquals(list3, list); + assertEquals(list, list3); assertEquals(list.hashCode(), list3.hashCode()); assertEquals(list.toString(), list3.toString()); diff --git a/src/test/java/io/reactivex/maybe/MaybeTest.java b/src/test/java/io/reactivex/maybe/MaybeTest.java index 41225a47cc..45b31b0a8a 100644 --- a/src/test/java/io/reactivex/maybe/MaybeTest.java +++ b/src/test/java/io/reactivex/maybe/MaybeTest.java @@ -3185,6 +3185,19 @@ public Publisher apply(Flowable v) throws Exception return (Publisher)v; } }).test().assertResult(1); + + final AtomicInteger calls = new AtomicInteger(); + try { + Maybe.error(new Callable() { + @Override + public Throwable call() { + calls.incrementAndGet(); + return new TestException(); + } + }).retry(5).test(); + } finally { + assertEquals(6, calls.get()); + } } @Test diff --git a/src/test/java/io/reactivex/observable/ObservableMergeTests.java b/src/test/java/io/reactivex/observable/ObservableMergeTests.java index c7ee01c591..1d68a5d28e 100644 --- a/src/test/java/io/reactivex/observable/ObservableMergeTests.java +++ b/src/test/java/io/reactivex/observable/ObservableMergeTests.java @@ -68,7 +68,7 @@ public void testMergeCovariance3() { assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); - assertTrue(values.get(2) != null); + assertNotNull(values.get(2)); assertTrue(values.get(3) instanceof HorrorMovie); } @@ -91,7 +91,7 @@ public Observable call() { assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); - assertTrue(values.get(2) != null); + assertNotNull(values.get(2)); assertTrue(values.get(3) instanceof HorrorMovie); } diff --git a/src/test/java/io/reactivex/observable/ObservableTest.java b/src/test/java/io/reactivex/observable/ObservableTest.java index ab915ffa53..f59a41857e 100644 --- a/src/test/java/io/reactivex/observable/ObservableTest.java +++ b/src/test/java/io/reactivex/observable/ObservableTest.java @@ -14,7 +14,6 @@ package io.reactivex.observable; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.*; diff --git a/src/test/java/io/reactivex/observable/ObservableWindowTests.java b/src/test/java/io/reactivex/observable/ObservableWindowTests.java index d4c68fd63b..701b07e0b7 100644 --- a/src/test/java/io/reactivex/observable/ObservableWindowTests.java +++ b/src/test/java/io/reactivex/observable/ObservableWindowTests.java @@ -16,11 +16,16 @@ import static org.junit.Assert.*; import java.util.*; +import java.util.concurrent.TimeUnit; import org.junit.Test; import io.reactivex.Observable; +import io.reactivex.SingleSource; import io.reactivex.functions.*; +import io.reactivex.observers.TestObserver; +import io.reactivex.schedulers.TestScheduler; +import io.reactivex.subjects.PublishSubject; public class ObservableWindowTests { @@ -50,4 +55,43 @@ public void accept(List xs) { assertEquals(2, lists.size()); } + + @Test + public void timeSizeWindowAlternatingBounds() { + TestScheduler scheduler = new TestScheduler(); + PublishSubject ps = PublishSubject.create(); + + TestObserver> to = ps.window(5, TimeUnit.SECONDS, scheduler, 2) + .flatMapSingle(new Function, SingleSource>>() { + @Override + public SingleSource> apply(Observable v) { + return v.toList(); + } + }) + .test(); + + ps.onNext(1); + ps.onNext(2); + to.assertValueCount(1); // size bound hit + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + ps.onNext(3); + scheduler.advanceTimeBy(6, TimeUnit.SECONDS); + to.assertValueCount(2); // time bound hit + + ps.onNext(4); + ps.onNext(5); + + to.assertValueCount(3); // size bound hit again + + ps.onNext(4); + + scheduler.advanceTimeBy(6, TimeUnit.SECONDS); + + to.assertValueCount(4) + .assertNoErrors() + .assertNotComplete(); + + to.dispose(); + } } diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index efd4642d7e..229dc7c873 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -2070,7 +2070,7 @@ public void run() { Thread t = value.get(); assertNotNull(t); - assertTrue(expectedThreadName.equals(t.getName())); + assertEquals(expectedThreadName, t.getName()); } catch (Exception e) { fail(); } finally { diff --git a/src/test/java/io/reactivex/processors/AsyncProcessorTest.java b/src/test/java/io/reactivex/processors/AsyncProcessorTest.java index 963171d30e..3d85d97eee 100644 --- a/src/test/java/io/reactivex/processors/AsyncProcessorTest.java +++ b/src/test/java/io/reactivex/processors/AsyncProcessorTest.java @@ -14,7 +14,6 @@ package io.reactivex.processors; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java b/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java index a0e3996f41..d465aa7a56 100644 --- a/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java +++ b/src/test/java/io/reactivex/processors/BehaviorProcessorTest.java @@ -14,7 +14,6 @@ package io.reactivex.processors; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/processors/MulticastProcessorTest.java b/src/test/java/io/reactivex/processors/MulticastProcessorTest.java index 63a7b1a60b..d541dffb3e 100644 --- a/src/test/java/io/reactivex/processors/MulticastProcessorTest.java +++ b/src/test/java/io/reactivex/processors/MulticastProcessorTest.java @@ -783,4 +783,41 @@ public void noUpstream() { assertTrue(mp.hasSubscribers()); } + @Test + public void requestUpstreamPrefetchNonFused() { + for (int j = 1; j < 12; j++) { + MulticastProcessor mp = MulticastProcessor.create(j, true); + + TestSubscriber ts = mp.test(0).withTag("Prefetch: " + j); + + Flowable.range(1, 10).hide().subscribe(mp); + + ts.assertEmpty() + .requestMore(3) + .assertValuesOnly(1, 2, 3) + .requestMore(3) + .assertValuesOnly(1, 2, 3, 4, 5, 6) + .requestMore(4) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + } + + @Test + public void requestUpstreamPrefetchNonFused2() { + for (int j = 1; j < 12; j++) { + MulticastProcessor mp = MulticastProcessor.create(j, true); + + TestSubscriber ts = mp.test(0).withTag("Prefetch: " + j); + + Flowable.range(1, 10).hide().subscribe(mp); + + ts.assertEmpty() + .requestMore(2) + .assertValuesOnly(1, 2) + .requestMore(2) + .assertValuesOnly(1, 2, 3, 4) + .requestMore(6) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + } } diff --git a/src/test/java/io/reactivex/processors/PublishProcessorTest.java b/src/test/java/io/reactivex/processors/PublishProcessorTest.java index f91e61551f..980ed84187 100644 --- a/src/test/java/io/reactivex/processors/PublishProcessorTest.java +++ b/src/test/java/io/reactivex/processors/PublishProcessorTest.java @@ -14,7 +14,6 @@ package io.reactivex.processors; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.ArrayList; diff --git a/src/test/java/io/reactivex/processors/ReplayProcessorTest.java b/src/test/java/io/reactivex/processors/ReplayProcessorTest.java index cf930b062a..b59d20fb94 100644 --- a/src/test/java/io/reactivex/processors/ReplayProcessorTest.java +++ b/src/test/java/io/reactivex/processors/ReplayProcessorTest.java @@ -14,7 +14,6 @@ package io.reactivex.processors; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.management.*; @@ -1051,7 +1050,7 @@ public void peekStateTimeAndSizeValueExpired() { scheduler.advanceTimeBy(2, TimeUnit.DAYS); - assertEquals(null, rp.getValue()); + assertNull(rp.getValue()); assertEquals(0, rp.getValues().length); assertNull(rp.getValues(new Integer[2])[0]); } @@ -1751,4 +1750,79 @@ public void accept(byte[] v) throws Exception { + " -> " + after.get() / 1024.0 / 1024.0); } } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange() { + ReplayProcessor rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestSubscriber ts = rp.test(); + + rp.onNext(1); + rp.cleanupBuffer(); + rp.onComplete(); + + ts.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange2() { + ReplayProcessor rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestSubscriber ts = rp.test(); + + rp.onNext(1); + rp.cleanupBuffer(); + rp.onNext(2); + rp.cleanupBuffer(); + rp.onComplete(); + + ts.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange3() { + ReplayProcessor rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestSubscriber ts = rp.test(); + + rp.onNext(1); + rp.onNext(2); + rp.onComplete(); + + ts.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange4() { + ReplayProcessor rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 10); + + TestSubscriber ts = rp.test(); + + rp.onNext(1); + rp.onNext(2); + rp.onComplete(); + + ts.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeRemoveCorrectNumberOfOld() { + TestScheduler scheduler = new TestScheduler(); + ReplayProcessor rp = ReplayProcessor.createWithTimeAndSize(1, TimeUnit.SECONDS, scheduler, 2); + + rp.onNext(1); + rp.onNext(2); + rp.onNext(3); + + scheduler.advanceTimeBy(2, TimeUnit.SECONDS); + + rp.onNext(4); + rp.onNext(5); + + rp.test().assertValuesOnly(4, 5); + } } diff --git a/src/test/java/io/reactivex/processors/UnicastProcessorTest.java b/src/test/java/io/reactivex/processors/UnicastProcessorTest.java index 5bbe6dca75..7b96e03b53 100644 --- a/src/test/java/io/reactivex/processors/UnicastProcessorTest.java +++ b/src/test/java/io/reactivex/processors/UnicastProcessorTest.java @@ -16,16 +16,20 @@ import static org.junit.Assert.*; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.Disposable; -import io.reactivex.exceptions.TestException; -import io.reactivex.internal.fuseable.*; +import io.reactivex.exceptions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.internal.subscriptions.BooleanSubscription; +import io.reactivex.observers.TestObserver; import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.*; public class UnicastProcessorTest extends FlowableProcessorTest { @@ -132,9 +136,9 @@ public void onTerminateCalledWhenOnError() { } }); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); us.onError(new RuntimeException("some error")); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test @@ -147,9 +151,9 @@ public void onTerminateCalledWhenOnComplete() { } }); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); us.onComplete(); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test @@ -164,9 +168,9 @@ public void onTerminateCalledWhenCanceled() { final Disposable subscribe = us.subscribe(); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); subscribe.dispose(); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test(expected = NullPointerException.class) @@ -438,4 +442,37 @@ public void unicastSubscriptionBadRequest() { RxJavaPlugins.reset(); } } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List errors = TestHelper.trackPluginErrors(); + try { + final UnicastProcessor us = UnicastProcessor.create(); + + TestObserver to = us + .observeOn(Schedulers.io()) + .map(Functions.identity()) + .observeOn(Schedulers.single()) + .firstOrError() + .test(); + + for (int i = 0; us.hasSubscribers(); i++) { + us.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java b/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java index 3605c74679..af489f5650 100644 --- a/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java +++ b/src/test/java/io/reactivex/schedulers/AbstractSchedulerConcurrencyTests.java @@ -405,7 +405,7 @@ public void accept(Integer t) { throw new RuntimeException("The latch should have released if we are async.", e); } - assertFalse(Thread.currentThread().getName().equals(currentThreadName)); + assertNotEquals(Thread.currentThread().getName(), currentThreadName); System.out.println("Thread: " + Thread.currentThread().getName()); System.out.println("t: " + t); count.incrementAndGet(); diff --git a/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java b/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java index b33efed40b..86ae8ac54e 100644 --- a/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java +++ b/src/test/java/io/reactivex/schedulers/ComputationSchedulerTests.java @@ -122,7 +122,7 @@ public final void testMergeWithExecutorScheduler() { @Override public String apply(Integer t) { - assertFalse(Thread.currentThread().getName().equals(currentThreadName)); + assertNotEquals(Thread.currentThread().getName(), currentThreadName); assertTrue(Thread.currentThread().getName().startsWith("RxComputationThreadPool")); return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); } diff --git a/src/test/java/io/reactivex/schedulers/TimedTest.java b/src/test/java/io/reactivex/schedulers/TimedTest.java index 7e8cb41e1a..c380188665 100644 --- a/src/test/java/io/reactivex/schedulers/TimedTest.java +++ b/src/test/java/io/reactivex/schedulers/TimedTest.java @@ -75,7 +75,7 @@ public void equalsWith() { assertNotEquals(new Object(), t1); - assertFalse(t1.equals(new Object())); + assertNotEquals(t1, new Object()); } @Test diff --git a/src/test/java/io/reactivex/schedulers/TrampolineSchedulerTest.java b/src/test/java/io/reactivex/schedulers/TrampolineSchedulerTest.java index 2768a12b1a..9f651bb50e 100644 --- a/src/test/java/io/reactivex/schedulers/TrampolineSchedulerTest.java +++ b/src/test/java/io/reactivex/schedulers/TrampolineSchedulerTest.java @@ -31,7 +31,6 @@ import org.reactivestreams.Subscriber; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; public class TrampolineSchedulerTest extends AbstractSchedulerTests { @@ -51,7 +50,7 @@ public final void testMergeWithCurrentThreadScheduler1() { @Override public String apply(Integer t) { - assertTrue(Thread.currentThread().getName().equals(currentThreadName)); + assertEquals(Thread.currentThread().getName(), currentThreadName); return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); } }); diff --git a/src/test/java/io/reactivex/single/SingleNullTests.java b/src/test/java/io/reactivex/single/SingleNullTests.java index ff7ddf19d0..059f106ffe 100644 --- a/src/test/java/io/reactivex/single/SingleNullTests.java +++ b/src/test/java/io/reactivex/single/SingleNullTests.java @@ -22,7 +22,6 @@ import org.reactivestreams.*; import io.reactivex.*; -import io.reactivex.SingleOperator; import io.reactivex.exceptions.*; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; diff --git a/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java b/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java index efd643e8d9..c1be4ab34c 100644 --- a/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/AsyncSubjectTest.java @@ -14,7 +14,6 @@ package io.reactivex.subjects; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; diff --git a/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java b/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java index 9a2b52f8f7..1a6f48a0e7 100644 --- a/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/BehaviorSubjectTest.java @@ -14,7 +14,6 @@ package io.reactivex.subjects; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List; diff --git a/src/test/java/io/reactivex/subjects/PublishSubjectTest.java b/src/test/java/io/reactivex/subjects/PublishSubjectTest.java index 7731d508f8..fc20c7b195 100644 --- a/src/test/java/io/reactivex/subjects/PublishSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/PublishSubjectTest.java @@ -14,7 +14,6 @@ package io.reactivex.subjects; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.ArrayList; diff --git a/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java b/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java index aa91270226..ce098ca16d 100644 --- a/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java +++ b/src/test/java/io/reactivex/subjects/ReplaySubjectTest.java @@ -14,7 +14,6 @@ package io.reactivex.subjects; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.management.*; @@ -965,7 +964,7 @@ public void peekStateTimeAndSizeValueExpired() { scheduler.advanceTimeBy(2, TimeUnit.DAYS); - assertEquals(null, rp.getValue()); + assertNull(rp.getValue()); assertEquals(0, rp.getValues().length); assertNull(rp.getValues(new Integer[2])[0]); } @@ -1343,4 +1342,79 @@ public void accept(byte[] v) throws Exception { + " -> " + after.get() / 1024.0 / 1024.0); } } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange() { + ReplaySubject rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestObserver to = rs.test(); + + rs.onNext(1); + rs.cleanupBuffer(); + rs.onComplete(); + + to.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange2() { + ReplaySubject rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestObserver to = rs.test(); + + rs.onNext(1); + rs.cleanupBuffer(); + rs.onNext(2); + rs.cleanupBuffer(); + rs.onComplete(); + + to.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange3() { + ReplaySubject rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 1); + + TestObserver to = rs.test(); + + rs.onNext(1); + rs.onNext(2); + rs.onComplete(); + + to.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeNoTerminalTruncationOnTimechange4() { + ReplaySubject rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, new TimesteppingScheduler(), 10); + + TestObserver to = rs.test(); + + rs.onNext(1); + rs.onNext(2); + rs.onComplete(); + + to.assertNoErrors() + .assertComplete(); + } + + @Test + public void timeAndSizeRemoveCorrectNumberOfOld() { + TestScheduler scheduler = new TestScheduler(); + ReplaySubject rs = ReplaySubject.createWithTimeAndSize(1, TimeUnit.SECONDS, scheduler, 2); + + rs.onNext(1); + rs.onNext(2); + rs.onNext(3); // remove 1 due to maxSize, size == 2 + + scheduler.advanceTimeBy(2, TimeUnit.SECONDS); + + rs.onNext(4); // remove 2 due to maxSize, remove 3 due to age, size == 1 + rs.onNext(5); // size == 2 + + rs.test().assertValuesOnly(4, 5); + } } diff --git a/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java b/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java index ca13ba0495..ea68601722 100644 --- a/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java +++ b/src/test/java/io/reactivex/subjects/UnicastSubjectTest.java @@ -17,16 +17,19 @@ import static org.mockito.Mockito.mock; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import io.reactivex.*; import io.reactivex.disposables.*; -import io.reactivex.exceptions.TestException; -import io.reactivex.internal.fuseable.*; +import io.reactivex.exceptions.*; +import io.reactivex.internal.functions.Functions; +import io.reactivex.internal.fuseable.QueueFuseable; import io.reactivex.observers.*; import io.reactivex.plugins.RxJavaPlugins; +import io.reactivex.schedulers.Schedulers; public class UnicastSubjectTest extends SubjectTest { @@ -168,9 +171,9 @@ public void onTerminateCalledWhenOnError() { } }); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); us.onError(new RuntimeException("some error")); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test @@ -183,9 +186,9 @@ public void onTerminateCalledWhenOnComplete() { } }); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); us.onComplete(); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test @@ -200,9 +203,9 @@ public void onTerminateCalledWhenCanceled() { final Disposable subscribe = us.subscribe(); - assertEquals(false, didRunOnTerminate.get()); + assertFalse(didRunOnTerminate.get()); subscribe.dispose(); - assertEquals(true, didRunOnTerminate.get()); + assertTrue(didRunOnTerminate.get()); } @Test(expected = NullPointerException.class) @@ -456,4 +459,37 @@ public void drainFusedFailFastEmpty() { to.assertEmpty(); } + + @Test + public void fusedNoConcurrentCleanDueToCancel() { + for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { + List errors = TestHelper.trackPluginErrors(); + try { + final UnicastSubject us = UnicastSubject.create(); + + TestObserver to = us + .observeOn(Schedulers.io()) + .map(Functions.identity()) + .observeOn(Schedulers.single()) + .firstOrError() + .test(); + + for (int i = 0; us.hasObservers(); i++) { + us.onNext(i); + } + + to + .awaitDone(5, TimeUnit.SECONDS) + ; + + if (!errors.isEmpty()) { + throw new CompositeException(errors); + } + + to.assertResult(0); + } finally { + RxJavaPlugins.reset(); + } + } + } } diff --git a/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java b/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java index c38105eb68..e0524350c9 100644 --- a/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java +++ b/src/test/java/io/reactivex/subscribers/SerializedSubscriberTest.java @@ -14,7 +14,6 @@ package io.reactivex.subscribers; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.List;

AltStyle によって変換されたページ (->オリジナル) /